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); } }
/// <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 } } }