嵌套两个lambda样式的函数表达式

本文关键字:样式 函数 表达式 lambda 两个 嵌套 | 更新日期: 2023-09-27 17:58:51

有没有一种简单的方法可以像下面这样组合两个lambda样式的表达式(我知道可以手动将示例表达式组合为一个,但如果innerExpression是由某个函数返回的,并且事先不知道该怎么办)。它们具有相同的输入参数类型,因此理论上ParameterExpression可以用于这两种类型。

Expression<Func<Source, Subtype>> innerExpression = x => new Subtype {
    Subfield1 = x.SomeField;
    Subfield2 = x.SomeOtherField;
}
Expression<Func<Source, Target>> finalExpression = x => new Target {
    Field1 = x.Other1,
    Field2 = x.Other2,
    Field3 = x.Items.Where(y => y.Field == true).SingleOrDefault(),
    Field4 = innerExpression(x) // <= Does not work that way
}

嵌套两个lambda样式的函数表达式

因此,我们在这里要做的是创建一个方法,该方法接受一个表达式,该表达式接受一个参数并计算另一个参数,然后另一个表达式接受与第一个参数相同的参数,即第一个函数的输出类型,然后计算一个全新的值。

这里的想法是,这个表达式将表示第一个函数的调用,然后第二个函数获取相同的输入值及其输出,并计算一个新值。然而,实际上,它不是在内部调用表达式,而是在使用表示其输出的参数的任何地方内联表达式。

public static Expression<Func<TFirstParam, TResult>>
    Combine<TFirstParam, TIntermediate, TResult>(
    this Expression<Func<TFirstParam, TIntermediate>> first,
    Expression<Func<TFirstParam, TIntermediate, TResult>> second)
{
    var param = Expression.Parameter(typeof(TFirstParam), "param");
    var newFirst = first.Body.Replace(first.Parameters[0], param);
    var newSecond = second.Body.Replace(second.Parameters[0], param)
        .Replace(second.Parameters[1], newFirst);
    return Expression.Lambda<Func<TFirstParam, TResult>>(newSecond, param);
}

这取决于以下用另一个表达式替换一个表达式的所有实例的方法:

internal class ReplaceVisitor : ExpressionVisitor
{
    private readonly Expression from, to;
    public ReplaceVisitor(Expression from, Expression to)
    {
        this.from = from;
        this.to = to;
    }
    public override Expression Visit(Expression node)
    {
        return node == from ? to : base.Visit(node);
    }
}
public static Expression Replace(this Expression expression,
    Expression searchEx, Expression replaceEx)
{
    return new ReplaceVisitor(searchEx, replaceEx).Visit(expression);
}

这个想法真的很简单。只需将表示输出的参数的所有实例替换为另一个方法的主体,同时确保两者之间的参数表达式一致。

现在我们可以这样写:

Expression<Func<Source, Subtype>> innerExpression = x => new Subtype
{
    Subfield1 = x.SomeField,
    Subfield2 = x.SomeOtherField,
};
Expression<Func<Source, Target>> finalExpression = innerExpression.Combine(
    (x, sub) => new Target
{
    Field1 = x.Other1,
    Field2 = x.Other2,
    Field3 = x.Items.Where(y => y.Field == true).SingleOrDefault(),
    Field4 = sub
});

@svick在评论中指出,正确的方法是使用LINQKit,这是一个支持构建表达式树的常见操作的库。

在你的情况下,你会有以下:

Expression<Func<Source, Subtype>> innerExpression = x => new Subtype {
    Subfield1 = x.SomeField;
    Subfield2 = x.SomeOtherField;
}
Expression<Func<Source, Target>> secondExpression = x => new Target {
    Field1 = x.Other1,
    Field2 = x.Other2,
    Field3 = x.Items.Where(y => y.Field == true).SingleOrDefault(),
    Field4 = innerExpression.Invoke(x)
}
Expression<Func<Source, Target>> finalExpression = secondExpression.Expand();