Combining two expressions into a pipeline

[亡魂溺海] 提交于 2019-12-01 21:46:08
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);

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?

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;
    }
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!