将两个表达式合并成一个管道

4

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

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

有办法“合并”它们以形成以下内容吗:(?)
Expression<Func<T, TNested>> selector;

编辑:

性能非常关键,因此如果可能的话,我希望得到一个开销非常小的最优解。

感谢您!

3个回答

7
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函数创建流水线表达式。就像这个伪代码示例:
   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);

我修改了答案 - Alex Aparin
第二个提出的解决方案创建委托和匿名对象,我认为从性能角度来看并不是很好..或者我漏掉了什么? - Eyal Perry
它将生成带有所需参数的调用函数,而不是使用动态调用。动态调用太慢了。 - Alex Aparin
我明白了。非常感谢,但我正在寻找的是不会产生匿名对象实例化的表达式级别解决方案。无论如何我已经为您点赞,因为这是一个解决方案。只是不是我想要的那个。 - Eyal Perry

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

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

编辑: 如果性能是您关心的问题,为什么要使用表达式而不是普通的Func?然后您可以一个接一个地调用它们。这些表达式树是否稍后被分析?


我不知道为什么这个解决方案没有问题。就是它!太完美了!谢谢。 - Gh61

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

1
通常最好解释一下解决方案,而不仅仅是发布一些匿名代码行。您可以阅读《如何撰写良好的答案》和《解释完全基于代码的答案》。 - Anh Pham

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接