Rewrite expression to replace List.Contains with custom method

In an attempt to expand my skill set, I am trying to learn how to rewrite expressions.

Purpose: Given the expression, I want to replace the List.Contains() instances with a call to my own static InList method. For example, the following two expressions should be equivalent:

 Expression<Func<Foo,bool>> expr1 = myRewriter.Rewrite(foo => fooList.Contains(foo)); Expression<Func<Foo,bool>> expr2 = foo => InList(foo, fooList); 

My attempt: I found out that using custom ExpressionVisitor is the best way to create a new expression based on an existing expression. However, I was unable to create a new MethodCallExpression that actually calls my method. Here is what I tried:

 public class InListRewriter<T> : ExpressionVisitor { public static bool InList(T target, List<T> source) { // this is my target method return true; } public Expression<Func<T, bool>> Rewrite(Expression<Func<T, bool>> expression) { return Visit(expression) as Expression<Func<T,bool>>; } protected override Expression VisitMethodCall(MethodCallExpression node) { // Only rewrite List.Contains() if (!node.Method.Name.Equals("Contains", StringComparison.InvariantCultureIgnoreCase)) return base.VisitMethodCall(node); // Extract parameters from original expression var sourceList = node.Object; // The list being searched var target = node.Method.GetParameters()[0]; // The thing being searched for // Create new expression var type = typeof (InListRewriter<T>); var methodName = "InList"; var typeArguments = new Type[] { }; var arguments = new[] { Expression.Parameter(target.ParameterType, target.Name), sourceList }; var newExpression = Expression.Call(type, methodName, typeArguments, arguments); return newExpression; } } 

However, when I call this through new InListRewriter<Foo>().Rewrite(foo => fooList.Contains(foo)) , I get an InvalidOperationException during Expression.Call :

There is no 'InList' method in type 'MyNamespace.InListRewriter`1 [MyNamespace.Foo]' is compatible with the arguments provided.

I even tried creating a new InList with an extremely common signature:

 public static bool InList(params object[] things) {...} 

But still the same error was received. What am I doing wrong? Is that what I want to do even possible?

+4
source share
1 answer

Your code has one big problem: the arguments it passes are incorrect, in particular the first one.

Instead of Expression.Parameter(target.ParameterType, target.Name) you need to use the actual argument: node.Arguments[0] .

Also, I suggest you use another Expression.Call overload: an overload that accepts MethodInfo .

The final code will look like this:

 protected override Expression VisitMethodCall(MethodCallExpression node) { // Only rewrite List.Contains() if (!node.Method.Name.Equals("Contains", StringComparison.InvariantCultureIgnoreCase)) return base.VisitMethodCall(node); // Extract parameters from original expression var sourceList = node.Object; // The list being searched var target = node.Arguments[0]; // The thing being searched for var newMethod = GetType().GetMethod("InList", BindingFlags.Static | BindingFlags.Public); // Create new expression var newExpression = Expression.Call(newMethod, target, sourceList); return newExpression; } 
+4
source

Source: https://habr.com/ru/post/1492125/


All Articles