Combining two expressions into a pipeline

2019-02-25 09:52发布

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!

3条回答
家丑人穷心不美
2楼-- · 2019-02-25 10:24
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);
查看更多
手持菜刀,她持情操
3楼-- · 2019-02-25 10:26
Expression<Func<TSourceType, TFinalType>> ChainExpressions<TSourceType, TIntermediaryType, TFinalType>(
        Expression<Func<TSourceType, TIntermediaryType>> firstExpression,
        Expression<Func<TIntermediaryType, TFinalType>> secondExpression
    )
    {
        var sourceInput = Expression.Parameter(typeof(TSourceType));
        var expressionForIntermediaryValue = Expression.Invoke(firstExpression, sourceInput);
        var expressionToGetTypedIntermediaryValue = Expression.Convert(expressionForIntermediaryValue, typeof(TIntermediaryType));

        var expressionForFinalValue = Expression.Invoke(secondExpression, expressionToGetTypedIntermediaryValue);
        var expressionToGetTypedFinalValue = Expression.Convert(expressionForFinalValue, typeof(TFinalType));

        var finalOutputExpression = Expression.Lambda(expressionToGetTypedFinalValue, sourceInput);
        return (Expression<Func<TSourceType, TFinalType>>)finalOutputExpression;
    }
查看更多
劫难
4楼-- · 2019-02-25 10:40

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?

查看更多
登录 后发表回答