【问题标题】:Nest two lambda style Func Expressions嵌套两个 lambda 样式的 Func 表达式
【发布时间】:2014-04-18 11:33:18
【问题描述】:

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

【问题讨论】:

  • 表达式必须在你调用它们之前被编译(参见this post的例子)。 Field4 是 SubType,而不是 Expression<...> 那么你不能简单地为它分配一个表达式。也就是说,这可能会阻止 finalExpression 的使用者按预期工作(例如,如果您使用的是 LINQ To SQL 或实体)。
  • 表达式永远不会被编译我需要表达式树来进行进一步的操作,手动创建表达式树会做更多的工作并且可读性会降低。但正如我所料,没有简单的方法可以将它们结合起来。
  • @Fionn is 实际上是一种(相对)简单的组合它们的方法。您可以以足够通用的方式编写表达式操作代码一次,然后您可以在此处执行您尝试执行的特定操作,而无需进行任何权限操作。
  • 您可以使用LINQKit 来执行此操作。

标签: c# .net lambda expression-trees


【解决方案1】:

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

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

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

【讨论】:

    【解决方案2】:

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

    【讨论】:

      猜你喜欢
      • 2016-07-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-03-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多