如何将表达式 Expression<Func<T1, T2, bool>> 合并为单个 Expression<Func<T2, bool>>

How to combine expressions Expression<Func<T1, T2, bool>> to a single Expression<Func<T2, bool>>

我有一个由两个 Func 组成的条件列表:

   public Func<TConfiguration, string> ConfigurationField { get;}
   public Func<TNumbering, string> NumberingField { get; }

对于每个条件,表达式如下所示:

Expression<Func<TNumbering, TConfiguration, bool>>  (n, c) => criteria.ConfigurationField(c) != criteria.NumberingField(n)

我需要用 OrElse 链接这些表达式的列表。

我试过做类似的事情:

BinaryExpression expression = null;

        foreach (var criteria in SelectionCriteria)
        {
            Expression<Func<TNumbering, TConfiguration, bool>> exp = (n, c) => criteria.ConfigurationField(c) != criteria.NumberingField(n);
            expression = expression == null ? exp : Expression.OrElse(expression, exp);
        }
        if (expression == null) return Result.Failure("Expression not defined"));
        var lambda = Expression.Lambda<Func<TConfiguration, bool>>(expression);
        numberingsToRemove = numberings.Where(_ => configurations.All(lambda));

但是,编译器不喜欢它,说 Expression.Lambda> 和二进制表达式之间没有隐式转换。

如果我用

 expression = expression == null ? Expression.OrElse(exp, exp) : Expression.OrElse(expression, exp);

我明白了

The binary operator OrElse is not defined for the types 'System.Func<TNumbering,TConfiguration,System.Boolean> and 'System.Func<TNumbering,TConfiguration,System.Boolean>.

我是构建表达式的新手,有人可以为我指明正确的方向吗?

你的Expression<Func<TNumbering, TConfiguration, bool>>是一个泛型类型,它的开放泛型类型是Expression<TDelegate>,其中TDelegate是一些委托类型;在这种情况下 Func<TNumbering, TConfiguration, bool>.

Expression 继承自 LambdaExpression, which represents a C# (or VB.NET) lambda expression.

就像你不会写下面的代码:

var result = 
    (n, c) => criteria.ConfigurationField(c) != criteria.NumberingField(n) ||
    (n1, c1) => criteria.ConfigurationField(c1) != criteria.NumberingField(n1);

尝试将两个 LambdaExpressionOrElse 结合起来会在运行时抛出异常。

您的代码甚至没有编译,因为 expression 被键入为 BinaryExpression,表示对应于此的表达式:

criteria.ConfigurationField(c) != criteria.NumberingField(n) ||
criteria.ConfigurationField(c1) != criteria.NumberingField(n1)

您试图将完整的 Expression 放入其中,其中包括(例如)参数列表。


每个LambdaExpression都有一个Body属性,从对应的表达式中提取:

(n, c) => criteria.ConfigurationField(c) != criteria.NumberingField(n)

LambdaExpression 的正文,或对应于此的表达式:

criteria.ConfigurationField(c) != criteria.NumberingField(n)

从理论上讲,您可以将其组合成对应于此的 BinaryExpression

criteria.ConfigurationField(c) != criteria.NumberingField(n) ||
criteria.ConfigurationField(c1) != criteria.NumberingField(n1)

但这也行不通,因为每次迭代都会引入多个新参数,所有这些参数都必须传递给最终的 lambda。


可以解决这个问题,但我建议您首先将 SelectionCriteria 中的每个元素映射到对应于条件评估的表达式,使用 [ 中的工厂方法=75=]。然后,您可以将这些表达式组合成 BinaryExpression,然后可以将其包裹在 LambdaExpression 甚至 Expression.

它可能看起来像这样(做出一些假设):

class Criteria<TConfiguration, TNumbering> {
    public Func<TConfiguration, string> ConfigurationField { get;}
    public Func<TNumbering, string> NumberingField { get; }
}

// using static System.Linq.Expressions.Expression;

var SelectionCritera = new List<Criteria>();

/*
 * populate list here
 */

var configParam = Parameter(typeof(TConfiguration));
var numberingParam = Parameter(typeof(TNumbering));
var expressions =
    SelectionCriteria.Select(criteria => {
        var criteriaExpr = Constant(criteria);

        return NotEqual(                   // !=
            Invoke(                        // ( ... )
                PropertyOrField(           // .ConfigurationField
                    criteriaExpr,          // criteria
                    "ConfigurationField"
                ),
                configParam                // c
            ),
            Invoke(                        // ( ... )
                PropertyOrField(           // .NumberingField
                    criteriaExpr,          // criteria
                    "NumberingField"
                ),
                numberingParam             // n
            )
        );  
    })
    .ToList();

if (!expressions.Any) { return Result.Failure("Expression not defined")); }

// Combine all the subexpressions using ||
var body = expressions.Aggregate((prev, next) => OrElse(prev, next));

// Create a LambdaExpression
var lmbd = Lambda<Func<TConfiguration, TNumbering, bool>>(body, configParam, numberingParam);

// Create a .NET method from the LambdaExpression
var mthd = lmbd.Compile();

// Apply the method to each config/numbering pair
var result = (
    from config in configs
    from numbering in numbering
    select (config, numbering)
).All(x => mthd(config, numbering));