嵌套两个lambda风格的Func表达式

3
有没有一种简单的方法来合并两个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
}

1
在调用表达式之前必须对其进行编译(请参见此帖子以获取示例)。Field4是SubType,而不是Expression<...>,因此您不能简单地将表达式分配给它。尽管如此,这可能会防止finalExpression的使用者按预期工作(例如,如果您正在使用LINQ To SQL或实体)。 - Adriano Repetti
1
表达式将永远不会被编译。我需要表达式树进行进一步操作,手动创建表达式树的工作量更大,可读性也较差。但正如我所预料的那样,没有简单的方法将它们组合起来。 - Fionn
@Fionn 实际上有一种(相对)简单的方法可以将它们结合起来。您可以以足够通用的方式编写表达式操作代码一次,从而使您能够执行您在此处尝试执行的特定操作,而无需进行任何权限操作。 - Servy
2
你可以使用LINQKit来完成这个任务。 - svick
2个回答

0
所以我们要做的是创建一个方法,它接受一个表达式作为参数并计算出另一个值,然后再创建另一个表达式来接受第一个函数相同的参数、第一个函数的输出类型,并计算出一个全新的值。
这里的想法是,这个表达式将代表第一个函数的调用,然后第二个函数将使用相同的输入值、第一个函数的输出值来计算一个新值。但实际上,它不会在内部调用表达式,而是在任何表示其输出的参数使用的地方内联表达式。
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
});

0
正如@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();

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