Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Combining two expressions into a pipeline

Let us say I have the following two expressions:

Expression<Func<T, IEnumerable<TNested>>> collectionSelector;
Expression<Func<IEnumerable<TNested>, TNested>> elementSelector;

Is there a way to "combine" these in order to form the below: (?)

Expression<Func<T, TNested>> selector;

EDIT:

Performance is very critical, so I would appreciate an optimal solution with very little overhead, if possible.

Many Thanks!

like image 303
Eyal Perry Avatar asked Dec 23 '22 23:12

Eyal Perry


2 Answers

static Expression<Func<A, C>> Foo<A, B, C>(
Expression<Func<B, C>> f,
Expression<Func<A, B>> g)
{
    var x = Expression.Parameter(typeof(A));
    return Expression.Lambda<Func<A, C>>(
        Expression.Invoke(f, Expression.Invoke(g, x)), x);
}

Unfortunately, I cannot access to computer ( it is bad solution in performance terms). Actually, I think that you can optimize code via call Expression.

Another way seems like that(usage auxiliary function):

Func<A, C> Foo<A,B,C>(Func<B, C> f, Func<A, B> g)
{ 
    return (A x) => f(g(x));
}

Then you should create pipeline Expression via call Expression with Foo function. Like that pseudo code:

   var expr1 = get expresstion<B,C> 
   var expr2 = get Expression<A, B>
   var foo = get method info of Foo method 
   specialize method with generic types A, B, C (via make generic method)
    return Expression.Call(foo, expr1, expr2);
like image 73
LmTinyToon Avatar answered Jan 08 '23 14:01

LmTinyToon


Another solution is to use ExpressionVisitor to replace the parameter in right expression with the whole left expression, in other words, embed the left one in the right one.

Expression visitor will be quite simple, add needed data to constructor, override one method and that's all.

internal sealed class ParameterReplaceVisitor : ExpressionVisitor
{
    private readonly ParameterExpression _searched;
    private readonly Expression _replaced;

    public ParameterReplaceVisitor(ParameterExpression searched, Expression replaced)
    {
        if (searched == null)
            throw new ArgumentNullException(nameof(searched));
        if (replaced == null)
            throw new ArgumentNullException(nameof(replaced));

        _searched = searched;
        _replaced = replaced;
    }

    protected override Expression VisitParameter(ParameterExpression node)
    {
        if (node == _searched)
            return _replaced;

        return base.VisitParameter(node);
    }
}

It can be quite easily extended to handle collections of expressions in the constructor, but I kept it short.

Now, you just need to use it on the expression bodies and construct new lambda.

private static Expression<Func<TIn, TOut>> Merge<TIn, TInter, TOut>(Expression<Func<TIn, TInter>> left, Expression<Func<TInter, TOut>> right)
{
    var merged = new ParameterReplaceVisitor(right.Parameters[0], left.Body).Visit(right.Body);

    var lambda = Expression.Lambda<Func<TIn, TOut>>(merged, left.Parameters[0]);

    return lambda;
}

I tested it on this code:

Expression<Func<string, int>> l = s => s.Length + 5;
Expression<Func<int, string>> r = i => i.ToString() + " something";

var merged = Merge(l, r);

var res = merged.Compile()("test");

and the result is as expected: 9 something.

EDIT: If performance is your concern, why are you using expressions instead of plain Funcs? Then you could just invoke one after another. Are the expression trees later analyzed?

like image 35
kiziu Avatar answered Jan 08 '23 14:01

kiziu