【问题标题】:Combining two expressions into a pipeline将两个表达式组合成管道
【发布时间】:2025-12-09 17:50:01
【问题描述】:

假设我有以下两个表达式:

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

有没有办法“组合”这些以形成以下内容:(?)

Expression<Func<T, TNested>> selector;

编辑:

性能非常关键,因此如果可能的话,我希望有一个开销很小的最佳解决方案。

非常感谢!

【问题讨论】:

    标签: c# expression combinators


    【解决方案1】:
    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);
    }
    

    不幸的是,我无法访问计算机(这在性能方面是不好的解决方案)。实际上,我认为您可以通过调用表达式来优化代码。

    另一种方式似乎是这样(使用辅助功能):

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

    然后您应该通过使用 Foo 函数调用 Expression 来创建管道表达式。就像那个伪代码:

       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);
    

    【讨论】:

    • 我修改了答案
    • 第二个提议的解决方案创建委托和匿名对象,我会说这在性能方面不是很好。或者我错过了什么?
    • 它将生成带有所需参数的调用函数,而不是使用动态调用。动态调用太慢了
    • 我明白了。非常感谢,但我正在寻找不会导致匿名对象实例化的表达式级别的东西。无论如何,我给了你一个赞成票,因为这是一个解决方案。只是不是我所追求的。
    【解决方案2】:

    另一种解决方案是使用ExpressionVisitor将右侧表达式中的参数替换为整个左侧表达式,也就是说,将左侧嵌入到右侧。

    表达式访问者将非常简单,将所需的数据添加到构造函数中,重写一个方法就可以了。

    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);
        }
    }
    

    它可以很容易地扩展来处理构造函数中的表达式集合,但我保持简短。

    现在,您只需在表达式主体上使用它并构造新的 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;
    }
    

    我在这段代码上测试过:

    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");
    

    结果如预期:9 something

    编辑: 如果您关心性能,为什么要使用表达式而不是简单的Funcs?然后你可以一个接一个地调用。后面会分析表达式树吗?

    【讨论】:

    • 我不知道为什么这个解决方案没有UP。就是这个!太棒了!谢谢。
    【解决方案3】:
    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;
        }
    

    【讨论】: