Example #1
0
 public static bool IsTypeMatcher(this Type type, out Type typeMatcherType)
 {
     if (type.IsTypeMatcher())
     {
         var attr = (TypeMatcherAttribute)Attribute.GetCustomAttribute(type, typeof(TypeMatcherAttribute));
         typeMatcherType = attr.Type ?? type;
         Guard.ImplementsTypeMatcherProtocol(typeMatcherType);
         return(true);
     }
     else
     {
         typeMatcherType = null;
         return(false);
     }
 }
Example #2
0
        /// <summary>
        ///   Splits an expression such as `<c>m => m.A.B(x).C[y] = z</c>` into a chain of parts
        ///   that can be set up one at a time:
        ///   <list>
        ///     <item>`<c>m => m.A</c>`</item>,
        ///     <item>`<c>... => ....B(x)</c>`</item>,
        ///     <item>`<c>... => ....C</c>`</item>,
        ///     <item>`<c>... => ...[y] = z</c>`</item>.
        ///   </list>
        ///   <para>
        ///     The split points are chosen such that each part has exactly one associated
        ///     <see cref="MethodInfo"/> and optionally some argument expressions.
        ///   </para>
        /// </summary>
        /// <exception cref="ArgumentException">
        ///   It was not possible to completely split up the expression.
        /// </exception>
        internal static Stack <InvocationShape> Split(this LambdaExpression expression)
        {
            Debug.Assert(expression != null);

            var parts = new Stack <InvocationShape>();

            Expression remainder = expression.Body;

            while (CanSplit(remainder))
            {
                Split(remainder, out remainder, out var part);
                parts.Push(part);
            }

            if (parts.Count > 0 && remainder is ParameterExpression)
            {
                return(parts);
            }
            else
            {
                throw new ArgumentException(
                          string.Format(
                              CultureInfo.CurrentCulture,
                              Resources.UnsupportedExpression,
                              remainder.ToStringFixed()));
            }

            bool CanSplit(Expression e)
            {
                switch (e.NodeType)
                {
                case ExpressionType.Assign:
                case ExpressionType.AddAssign:
                case ExpressionType.SubtractAssign:
                {
                    var assignmentExpression = (BinaryExpression)e;
                    return(CanSplit(assignmentExpression.Left));
                }

                case ExpressionType.Call:
                case ExpressionType.Index:
                {
                    return(true);
                }

                case ExpressionType.Invoke:
                {
                    var invocationExpression = (InvocationExpression)e;
                    return(typeof(Delegate).IsAssignableFrom(invocationExpression.Expression.Type));
                }

                case ExpressionType.MemberAccess:
                {
                    var memberAccessExpression = (MemberExpression)e;
                    return(memberAccessExpression.Member is PropertyInfo);
                }

                case ExpressionType.Parameter:
                default:
                {
                    return(false);
                }
                }
            }

            void Split(Expression e, out Expression r /* remainder */, out InvocationShape p /* part */)
            {
                const string ParameterName = "...";

                switch (e.NodeType)
                {
                case ExpressionType.Assign:                                  // assignment to a property or indexer
                case ExpressionType.AddAssign:                               // subscription of event handler to event
                case ExpressionType.SubtractAssign:                          // unsubscription of event handler from event
                {
                    var assignmentExpression = (BinaryExpression)e;
                    Split(assignmentExpression.Left, out r, out var lhs);
                    PropertyInfo property;
                    if (lhs.Expression.Body is MemberExpression me)
                    {
                        Debug.Assert(me.Member is PropertyInfo);
                        property = (PropertyInfo)me.Member;
                    }
                    else
                    {
                        Debug.Assert(lhs.Expression.Body is IndexExpression);
                        property = ((IndexExpression)lhs.Expression.Body).Indexer;
                    }
                    var parameter = Expression.Parameter(r.Type, r is ParameterExpression ope ? ope.Name : ParameterName);
                    var arguments = new Expression[lhs.Arguments.Count + 1];
                    for (var ai = 0; ai < arguments.Length - 1; ++ai)
                    {
                        arguments[ai] = lhs.Arguments[ai];
                    }
                    arguments[arguments.Length - 1] = assignmentExpression.Right;
                    p = new InvocationShape(
                        expression: Expression.Lambda(
                            Expression.MakeBinary(e.NodeType, lhs.Expression.Body, assignmentExpression.Right),
                            parameter),
                        method: property.GetSetMethod(true),
                        arguments);
                    return;
                }

                case ExpressionType.Call:                          // regular method call
                {
                    var methodCallExpression = (MethodCallExpression)e;

                    if (methodCallExpression.Method.IsGenericMethod)
                    {
                        foreach (var typeArgument in methodCallExpression.Method.GetGenericArguments())
                        {
                            if (typeArgument.IsTypeMatcher(out var typeMatcherType))
                            {
                                Guard.ImplementsTypeMatcherProtocol(typeMatcherType);
                            }
                        }
                    }

                    if (!methodCallExpression.Method.IsStatic)
                    {
                        r = methodCallExpression.Object;
                        var parameter = Expression.Parameter(r.Type, r is ParameterExpression ope ? ope.Name : ParameterName);
                        var method    = methodCallExpression.Method;
                        var arguments = methodCallExpression.Arguments;
                        p = new InvocationShape(
                            expression: Expression.Lambda(
                                Expression.Call(parameter, method, arguments),
                                parameter),
                            method,
                            arguments);
                    }
                    else
                    {
                        Debug.Assert(methodCallExpression.Method.IsExtensionMethod());
                        Debug.Assert(methodCallExpression.Arguments.Count > 0);
                        r = methodCallExpression.Arguments[0];
                        var parameter = Expression.Parameter(r.Type, r is ParameterExpression ope ? ope.Name : ParameterName);
                        var method    = methodCallExpression.Method;
                        var arguments = methodCallExpression.Arguments.ToArray();
                        arguments[0] = parameter;
                        p            = new InvocationShape(
                            expression: Expression.Lambda(
                                Expression.Call(method, arguments),
                                parameter),
                            method,
                            arguments);
                    }
                    return;
                }

                case ExpressionType.Index:                          // indexer query
                {
                    var indexExpression = (IndexExpression)e;
                    r = indexExpression.Object;
                    var parameter = Expression.Parameter(r.Type, r is ParameterExpression ope ? ope.Name : ParameterName);
                    var indexer   = indexExpression.Indexer;
                    var arguments = indexExpression.Arguments;
                    p = new InvocationShape(
                        expression: Expression.Lambda(
                            Expression.MakeIndex(parameter, indexer, arguments),
                            parameter),
                        method: indexer.GetGetMethod(true),
                        arguments);
                    return;
                }

                case ExpressionType.Invoke:                          // delegate invocation
                {
                    var invocationExpression = (InvocationExpression)e;
                    Debug.Assert(invocationExpression.Expression.Type.IsDelegateType());
                    r = invocationExpression.Expression;
                    var parameter = Expression.Parameter(r.Type, r is ParameterExpression ope ? ope.Name : ParameterName);
                    var arguments = invocationExpression.Arguments;
                    p = new InvocationShape(
                        expression: Expression.Lambda(
                            Expression.Invoke(parameter, arguments),
                            parameter),
                        method: r.Type.GetMethod("Invoke", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance),
                        arguments);
                    return;
                }

                case ExpressionType.MemberAccess:                          // property query
                {
                    var memberAccessExpression = (MemberExpression)e;
                    Debug.Assert(memberAccessExpression.Member is PropertyInfo);
                    r = memberAccessExpression.Expression;
                    var parameter = Expression.Parameter(r.Type, r is ParameterExpression ope ? ope.Name : ParameterName);
                    var property  = memberAccessExpression.GetReboundProperty();
                    var method    = property.CanRead(out var getter) ? getter : property.GetSetMethod(true);
                    //                    ^^^^^^^                            ^^^^^^^^^^^^^^^^^^^^^^^^^^^
                    // We're in the switch case block for property read access, therefore we prefer the
                    // getter. When a read-write property is being assigned to, we end up here, too, and
                    // select the wrong accessor. However, that doesn't matter because it will be over-
                    // ridden in the above `Assign` case. Finally, if a write-only property is being
                    // assigned to, we fall back to the setter here in order to not end up without a
                    // method at all.
                    p = new InvocationShape(
                        expression: Expression.Lambda(
                            Expression.MakeMemberAccess(parameter, property),
                            parameter),
                        method);
                    return;
                }

                default:
                    Debug.Assert(!CanSplit(e));
                    throw new InvalidOperationException();                              // this should be unreachable
                }
            }
        }
        /// <summary>
        ///   Splits an expression such as `<c>m => m.A.B(x).C[y] = z</c>` into a chain of parts
        ///   that can be set up one at a time:
        ///   <list>
        ///     <item>`<c>m => m.A</c>`</item>,
        ///     <item>`<c>... => ....B(x)</c>`</item>,
        ///     <item>`<c>... => ....C</c>`</item>,
        ///     <item>`<c>... => ...[y] = z</c>`</item>.
        ///   </list>
        ///   <para>
        ///     The split points are chosen such that each part has exactly one associated
        ///     <see cref="MethodInfo"/> and optionally some argument expressions.
        ///   </para>
        /// </summary>
        /// <exception cref="ArgumentException">
        ///   It was not possible to completely split up the expression.
        /// </exception>
        internal static Stack <InvocationShape> Split(this LambdaExpression expression, bool allowNonOverridableLastProperty = false)
        {
            Debug.Assert(expression != null);

            var parts = new Stack <InvocationShape>();

            Expression remainder = expression.Body;

            while (CanSplit(remainder))
            {
                Split(remainder, out remainder, out var part, allowNonOverridableLastProperty: allowNonOverridableLastProperty && parts.Count == 0);
                parts.Push(part);
            }

            if (parts.Count > 0 && remainder is ParameterExpression)
            {
                return(parts);
            }
            else
            {
                throw new ArgumentException(
                          string.Format(
                              CultureInfo.CurrentCulture,
                              Resources.UnsupportedExpression,
                              remainder.ToStringFixed()));
            }

            void Split(Expression e, out Expression r /* remainder */, out InvocationShape p /* part */, bool assignment = false, bool allowNonOverridableLastProperty = false)
            {
                const string ParameterName = "...";

                switch (e.NodeType)
                {
                case ExpressionType.Assign:                                  // assignment to a property or indexer
                case ExpressionType.AddAssign:                               // subscription of event handler to event
                case ExpressionType.SubtractAssign:                          // unsubscription of event handler from event
                {
                    var assignmentExpression = (BinaryExpression)e;
                    Split(assignmentExpression.Left, out r, out var lhs, assignment: true);
                    var parameter = Expression.Parameter(r.Type, r is ParameterExpression ope ? ope.Name : ParameterName);
                    var arguments = new Expression[lhs.Method.GetParameters().Length];
                    for (var ai = 0; ai < arguments.Length - 1; ++ai)
                    {
                        arguments[ai] = lhs.Arguments[ai];
                    }
                    arguments[arguments.Length - 1] = assignmentExpression.Right;
                    p = new InvocationShape(
                        expression: Expression.Lambda(
                            Expression.MakeBinary(e.NodeType, lhs.Expression.Body, assignmentExpression.Right),
                            parameter),
                        method: lhs.Method,
                        arguments);
                    return;
                }

                case ExpressionType.Call:                          // regular method call
                {
                    var methodCallExpression = (MethodCallExpression)e;

                    if (methodCallExpression.Method.IsGenericMethod)
                    {
                        foreach (var typeArgument in methodCallExpression.Method.GetGenericArguments())
                        {
                            if (typeArgument.IsTypeMatcher(out var typeMatcherType))
                            {
                                Guard.ImplementsTypeMatcherProtocol(typeMatcherType);
                            }
                        }
                    }

                    if (!methodCallExpression.Method.IsStatic)
                    {
                        r = methodCallExpression.Object;
                        var parameter = Expression.Parameter(r.Type, r is ParameterExpression ope ? ope.Name : ParameterName);
                        var method    = methodCallExpression.Method;
                        var arguments = methodCallExpression.Arguments;
                        p = new InvocationShape(
                            expression: Expression.Lambda(
                                Expression.Call(parameter, method, arguments),
                                parameter),
                            method,
                            arguments);
                    }
                    else
                    {
                        Debug.Assert(methodCallExpression.Method.IsExtensionMethod());
                        Debug.Assert(methodCallExpression.Arguments.Count > 0);
                        r = methodCallExpression.Arguments[0];
                        var parameter = Expression.Parameter(r.Type, r is ParameterExpression ope ? ope.Name : ParameterName);
                        var method    = methodCallExpression.Method;
                        var arguments = methodCallExpression.Arguments.ToArray();
                        arguments[0] = parameter;
                        p            = new InvocationShape(
                            expression: Expression.Lambda(
                                Expression.Call(method, arguments),
                                parameter),
                            method,
                            arguments);
                    }
                    return;
                }

                case ExpressionType.Index:                          // indexer query
                {
                    var indexExpression = (IndexExpression)e;
                    r = indexExpression.Object;
                    var parameter = Expression.Parameter(r.Type, r is ParameterExpression ope ? ope.Name : ParameterName);
                    var indexer   = indexExpression.Indexer;
                    var arguments = indexExpression.Arguments;
                    var method    = !assignment && indexer.CanRead(out var getter)  ? getter
                                                           :                indexer.CanWrite(out var setter) ? setter
                                                           :                                                   null;
                    p = new InvocationShape(
                        expression: Expression.Lambda(
                            Expression.MakeIndex(parameter, indexer, arguments),
                            parameter),
                        method,
                        arguments,
                        skipMatcherInitialization: assignment,
                        allowNonOverridable: allowNonOverridableLastProperty);
                    return;
                }

                case ExpressionType.Invoke:                          // delegate invocation
                {
                    var invocationExpression = (InvocationExpression)e;
                    Debug.Assert(invocationExpression.Expression.Type.IsDelegateType());
                    r = invocationExpression.Expression;
                    var parameter = Expression.Parameter(r.Type, r is ParameterExpression ope ? ope.Name : ParameterName);
                    var arguments = invocationExpression.Arguments;
                    p = new InvocationShape(
                        expression: Expression.Lambda(
                            Expression.Invoke(parameter, arguments),
                            parameter),
                        method: r.Type.GetMethod("Invoke", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance),
                        arguments);
                    return;
                }

                case ExpressionType.MemberAccess:                          // property query
                {
                    var memberAccessExpression = (MemberExpression)e;
                    Debug.Assert(memberAccessExpression.Member is PropertyInfo);
                    r = memberAccessExpression.Expression;
                    var parameter = Expression.Parameter(r.Type, r is ParameterExpression ope ? ope.Name : ParameterName);
                    var property  = memberAccessExpression.GetReboundProperty();
                    var method    = !assignment && property.CanRead(out var getter)  ? getter
                                                           :                property.CanWrite(out var setter) ? setter
                                                           :                                                    null;
                    p = new InvocationShape(
                        expression: Expression.Lambda(
                            Expression.MakeMemberAccess(parameter, property),
                            parameter),
                        method,
                        skipMatcherInitialization: assignment,
                        allowNonOverridable: allowNonOverridableLastProperty);
                    return;
                }

                default:
                    Debug.Assert(!CanSplit(e));
                    throw new InvalidOperationException();                              // this should be unreachable
                }
            }
        }