Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to call the method from a MethodCallExpression in c#

I have a method call expression and try to invoke the method. I figured out a way, but I have problems in retrieving the parameter values since not every argument is described with a ConstantExpression.

Expression<Action<T>> = t => t.DoSomething(Par0, Par1, Par2);
MethodCallExpression methodCallExpression = selector.Body 
                                               as MethodCallExpression;

// get the information which is needed to invoke the method from the provided 
// lambda expression.
MethodInfo methodInfo = methodCallExpression.Method;
object[] arguments = methodCallExpression.Arguments.OfType<ConstantExpression>()
                            .Select(p => p.Value).ToArray();

// invoke the expression on every item within the enumerable
foreach (TSource item in source)
{ 
    methodInfo.Invoke(item, arguments);
}

Additionally, I have seen some other ways to invoke the method, now I'm not sure what is the right way to do it.

var func = expression.Compile();
var success = func.Invoke();

So my question is, how can I retrieve the method argument values from methodCallExpression.Arguments?

Or is there an easier way to achieve my goal?

like image 726
Enyra Avatar asked Apr 22 '09 09:04

Enyra


4 Answers

You don't need to worry about retrieving the arguments and calling the MethodInfo yourself, you can let .NET do it for you. All you need to do is create a Lambda expression containing that method.

eg.

MethodCallExpression expression = GetExpressionSomeHow();
object result = Expression.Lambda(expression).Compile().DynamicInvoke();

That's how I deal with nested queries in my Linq provider anyway.

EDIT: Actually, it looks like you might already have a LambdaExpression in the selector variable. In that case, you should be able to just compile and invoke it directly:

object result = selector.Compile().DynamicInvoke();
like image 155
David Wengier Avatar answered Sep 26 '22 08:09

David Wengier


Compiling an expression is a very intensive operation, so I would only do that if you are planning on re-using the expression. I would recommend the reflection way otherwise; you will find it executes faster. Never call expression.Compile() in a tight loop.

like image 20
user179338 Avatar answered Sep 25 '22 08:09

user179338


@Ch00k <-- Thanks, nice explanation. I would just like to add that

selector.Compile();

gives you a delegate. For an instance method you need an instance on which to call this method. You pass this instance as the argument to DynamicInvoke ala

// Grab the method from MyClass - param1 and param2 are the actual parameters you
// want to pass to the method call.
Expression<Func<MyClass, TValue>> selector = (x => x.MyMethod(param1, param2));

// Create an instance of MyClass to call the method on
var myClass = new MyClass();

// Call the method on myClass through DynamicInvoke
object returnValue = selector.Compile().DynamicInvoke(myClass);
like image 34
satnhak Avatar answered Sep 25 '22 08:09

satnhak


If you want to compile your expression.call into a Action or Func, this is how you do:

var method = typeof(MyType).GetMethod(nameof(MyType.MyMethod), BindingFlags.Public | BindingFlags.Static);
var parameter = Expression.Parameter(typeof(string), "s");
var call = Expression.Call(method, parameter);
var lambda = Expression.Lambda<Func<string, int>>(call, call.Arguments.OfType<ParameterExpression>());
var func = lambda.Compile();
int result = func("sample string input");

This allows you to simply do func.Invoke("mystring") or func("my string");

The secret here is you need to pass the same parameters you used when creating the Expression.Call, otherwise you get an error of type "InvalidOperationException" variable 's' of type 'System.String' referenced from scope '', but it is not defined.

like image 26
Rafael Diego Nicoletti Avatar answered Sep 26 '22 08:09

Rafael Diego Nicoletti