1

I want to capture a method call as an expression, but evaluate its arguments in a type-safe and IDE-friendly way beforehand.

Take the following example class:

class Test
{
    public string SomeMethod(int a, string b) { ... }
}

I now want to pass a call to SomeMethod to a function receiving an Expression:

HandleMethodCall<Test>(test => test.SomeMethod("string".Length, 5.ToString()));

Right now HandleMethodCall has the signature

static void HandleMethodCall<T>(Expression<Func<T, object>> expr)

Unfortunately, in this case the expression passed to HandleMethodCall does not only contain test.SomeMethod(), but expression trees for the arguments as well.

A workaround would be to split the method call and its arguments into different arguments for HandleMethodCall, but this has the cost of losing compile-time type-safety and IDE support for showing argument names.

Is there any way to make the compiler evaluate parts of an expression before putting them into an Expression object?

Or, alternatively, is it possible to manually invoke evaluation of the expression trees for the method parameters?

janw
  • 8,758
  • 11
  • 40
  • 62
  • 1
    I'm not sure if this is what you mean by, "manually invoke evaluation of the expression trees for the method parameters" - (https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/expression-trees/how-to-execute-expression-trees) ? – Ryan Wilson Jan 30 '20 at 16:29
  • 1
    Hmm, that might work - so you propose creating a `Expression>` from the expression tree of each argument and run it? – janw Jan 30 '20 at 16:36
  • 1
    If that is what you meant by your post, then that is the only thing I can think of to do for evaluating your parameters. – Ryan Wilson Jan 30 '20 at 16:41
  • Of course, you will have an issue trying to get the type `T` for the arguments at compile time. Why do you need to evaluate the arguments separately from the call? – NetMage Jan 30 '20 at 18:53
  • @NetMage Yes, I had to work around using `Func` and `Expression.Convert()`. I will self-answer after completing my experiments, if no one else does :) I try to build a type-safe variant of `Url.Action()` for ASP.NET Core (follow up for [this question](https://stackoverflow.com/questions/59969091/using-nameof-with-url-action-and-async-methods-in-asp-net-core-3-x-mvc)). – janw Jan 30 '20 at 18:59

1 Answers1

1

It looks like it is not possible to specify this at compile time, but one can compile and evaluate the expression parts at runtime using Expression.Lambda<>(...).Compile()() (see documentation).

This results in the following implementation (no error checking for sake of simplicity):

public static void HandleMethodCall<T>(Expression<Func<T, object>> expr)
{
    // Extract method call
    var methodCall = (MethodCallExpression)expr.Body;

    // Run through expected arguments and evaluate the supplied ones
    var expectedArguments = methodCall.Method.GetParameters();
    for(int i = 0; i < expectedArguments.Length; ++i)
    {
        Console.Write(expectedArguments[i].Name + ": ");

        // Since we do not know the argument type in advance, we have to use Func<object>
        var argumentEvaluationExpression = Expression.Lambda<Func<object>>(Expression.Convert(methodCall.Arguments[i], typeof(object)));
        object argumentValue = argumentEvaluationExpression.Compile()();

        Console.WriteLine(argumentValue);
    }
}

Calling this function with

HandleMethodCall<Test>(test => test.SomeMethod("string".Length, "1" + " 2 " + (1 + 2).ToString()));

outputs the expected result

a: 6
b: 1 2 3

However, I am not sure whether this will really work under all conditions. In addition, I expect this to be quite slow due to the Compile() step.

Thanks to @RyanWilson for the helpful comments, which allowed me to figure out this solution!

janw
  • 8,758
  • 11
  • 40
  • 62