Search code examples
c#lambdaexpression-trees

Use value of property expression inside expression tree


Consider a property expression like t => t.MyProperty where t is of type MyClass. How can I use this property expression inside a new expression where I perform a method call?

Pure C#:

class MyClass
{
    public string MyProperty { get; set; }
}

static void Foo(string foo)
{   
}

LambdaExpression GetExpression(Expression<Func<MyClass, object>> expr)
{
    return expr;
}

var myClass = new MyClass();
Foo(myClass.MyProperty);

Now with expressions...?

var expr = GetExpression(m => m.MyProperty);
var mi = typeof(Program).GetMethod(nameof(Program.Foo),
    BindingFlags.Public | BindingFlags.Static);

var myClass = new MyClass();
// Now what??
// var call = Expression.Call(mi, ???expr??);
// var invoke = Expression.Invoke(call, fooParameter);

I want to use the result of expr and use that in the call to Foo. I know I can do this in two steps, where I call expr.Compile().DynamicInvoke(myClass) to get the value, but that is not what I'm asking for here.

I want to build an expression that takes a property getter expression and then performs a call to Foo(result of expression). I cannot figure out how to use the expression as a parameter to the method call.


Solution

  • There's two ways of doing this, depending on the complexity. In this case, we can re-use the parameter from the inner expression - bubbling it outwards; we do this by discarding our old lambda, just using the .Body and .Parameters. For example:

    var call = Expression.Lambda<Action<MyClass>>(
        Expression.Call(mi, expr.Body), expr.Parameters);
    
    var myClass = new MyClass { MyProperty = "yay!" };
    call.Compile().Invoke(myClass);
    

    The other way is to use Invoke on the inner lambda:

    var outerParameter = Expression.Parameter(typeof(MyClass));
    var typed = Expression.Convert(Expression.Invoke(expr, outerParameter), typeof(string));
    var call = Expression.Lambda<Action<MyClass>>(Expression.Call(mi, typed), outerParameter);
    

    The second form (Invoke) is useful when you can't conveniently control the parameters in the two places - or where, for example, you have multiple inner expressions with different parameter instances.