How to call the method from a MethodCallExpression in c#

21,564

Solution 1

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();

Solution 2

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.

Solution 3

@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);

Solution 4

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.

Share:
21,564
Enyra
Author by

Enyra

Updated on July 09, 2022

Comments

  • Enyra
    Enyra almost 2 years

    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?