Create dynamic Expression lambda from two others (chaining the Expressions)
Asked Answered
K

2

9

Given a lambda that takes an Identification object, and returns a property:

Expression<Func<Identification, object>> fx = _ => _.Id;

And a conversion lambda that converts an object into an Identification instance:

ParameterExpression p = Expression.Parameter(typeof(object), "o");
Expression @new = Expression.Lambda(Expression.Convert(p, typeof(Identification)), p);

How do I build a new lambda that executes @new (getting out the Identification Instance) and passes the result into fx. I need @new's result to bind to the first parameter of fx somehow, and I cannot find an example.

I need the result to be an Expression, it should be of type Expression<Func<object, object>> and it should convert the inbound parameter to an Identification and then get the Id property.

Konrad answered 24/10, 2011 at 9:16 Comment(0)
V
10

Firstly, note that this is easier if you type @new appropriately, i.e.:

LambdaExpression @new = ...

since that provides easy access to @new.Body and @new.Parameters; that done, Expression.Invoke can be useful here:

var combinedParam = Expression.Parameter(typeof(object), "o");
var combined = Expression.Lambda<Func<object, object>>(
    Expression.Invoke(fx,
        Expression.Invoke(@new, combinedParam)), combinedParam);

although for a cleaner expression, you can also use ExpressionVisitor to completely replace the inner expressions:

var injected = new SwapVisitor(fx.Parameters[0], @new.Body).Visit(fx.Body);
var combined = Expression.Lambda<Func<object, object>>(injected,@new.Parameters);

with:

class SwapVisitor : ExpressionVisitor {
    private readonly Expression from, to;
    public SwapVisitor(Expression from, Expression to) {
        this.from = from;
        this.to = to;
    }
    public override Expression Visit(Expression node) {
        return node == from ? to : base.Visit(node);
    }
}

what this does is:

  • inspect the fx.Body tree, replacing all instances of _ (the parameter) with the @new.Body (note that this will contain references to the o parameter (aka p)
  • we then build a new lambda from the replaced expression, re-using the same parameters from @new, which ensures that the values we injected will be bound correctly
Vigesimal answered 24/10, 2011 at 9:33 Comment(3)
Hi, I actually came close to trying exactly that, but it looked all wrong in linqpad. The final expression tree looks horrific, even when simplified - but it does the job perfectly thanks. If you do it all at once the compiler generates a neater tree. (object p) => ((Identification)p).Id you get Convert(Convert(p).Id) which is really neat. Anyway, thanks for the answer.Konrad
@Konrad - have you refreshed? the second version creates exactly that o => Convert(Convert(o).Id), rather than o => Invoke(_ => Convert(_.Id), Invoke(o => Convert(o), o))Vigesimal
I have used the visitor, and generated the new expression. It works perfectly thanks.Konrad
Z
6

Using the code from Marc Gravell's answer, you can simplify this really nicely with a helper function:

public static class ExpressionHelper {
    public static Expression<Func<TFrom, TTo>> Chain<TFrom, TMiddle, TTo>(
        this Expression<Func<TFrom, TMiddle>> first,
        Expression<Func<TMiddle, TTo>> second
    ) {
        return Expression.Lambda<Func<TFrom, TTo>>(
           new SwapVisitor(second.Parameters[0], first.Body).Visit(second.Body),
           first.Parameters
        );
    }

    private class SwapVisitor : ExpressionVisitor {
        private readonly Expression _from;
        private readonly Expression _to;

        public SwapVisitor(Expression from, Expression to) {
            _from = from;
            _to = to;
        }

        public override Expression Visit(Expression node) {
            return node == _from ? _to : base.Visit(node);
        }
    }
}

Now look how clean that is!

var valueSelector = new Expression<Func<MyTable, int>>(o => o.Value);
var intSelector = new Expression<Func<int, bool>>(x => x > 5);
var selector = valueSelector.Chain<MyTable, int, bool>(intSelector);

And it works with Entity Framework and other things that need a clean Expression that doesn't try to invoke a Func within it.

Zerk answered 30/10, 2015 at 18:46 Comment(1)
This answer does not rank highly enough on Google. Though searching extensively I only found this question and this elegant answer from a blog post that linked it. Here's hoping new activity might help future searchers.Irrelevancy

© 2022 - 2024 — McMap. All rights reserved.