How do I compose Linq Expressions? ie Func<Exp<Func<X, Y>>, Exp<Func<Y, Z>>, Exp<Func<X, Z>>>

烈酒焚心 提交于 2019-11-28 19:38:57

The equivalent of Haskell's function composition operator

(.) :: (b->c) -> (a->b) -> (a->c)
f . g = \ x -> f (g x)

would in C# probably be something like

static Expression<Func<A, C>> Compose<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);
}

Is this what you're looking for?

Example:

Compose<int, int, string>(y => y.ToString(), x => x + 1).Compile()(10); // "11"
felipe

While dtb's answer works for several scenarios, it is suboptimal as such an expression cannot be used in Entity Framework, as it cannot handle Invoke calls. Unfortunately, to avoid those calls one needs a lot more code, including a new ExpressionVisitor derived class:

static Expression<Func<A, C>> Compose<A, B, C>(Expression<Func<B, C>> f,
                                               Expression<Func<A, B>> g)
{
    var ex = ReplaceExpressions(f.Body, f.Parameters[0], g.Body);

    return Expression.Lambda<Func<A, C>>(ex, g.Parameters[0]);
}

static TExpr ReplaceExpressions<TExpr>(TExpr expression,
                                       Expression orig,
                                       Expression replacement)
    where TExpr : Expression 
{
    var replacer = new ExpressionReplacer(orig, replacement);

    return replacer.VisitAndConvert(expression, nameof(ReplaceExpressions));
}

private class ExpressionReplacer : ExpressionVisitor
{
    private readonly Expression From;
    private readonly Expression To;

    public ExpressionReplacer(Expression from, Expression to)
    {
        From = from;
        To = to;
    }

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

This replaces every instance of the first parameter in the first expression with the expression in the second expression. So a call like this:

Compose((Class1 c) => c.StringProperty, (Class2 c2) => c2.Class1Property

Would yield the expression (Class2 c2) => c2.Class1Property.StringProperty.

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