【问题标题】:How to combine expressions Expression<Func<T1, T2, bool>> to a single Expression<Func<T2, bool>>如何将表达式 Expression<Func<T1, T2, bool>> 组合成一个 Expression<Func<T2, bool>>
【发布时间】:2021-10-14 17:17:32
【问题描述】:

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

   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> 和 Binary 表达式之间没有隐式转换。

如果我使用

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

我明白了

没有为 'System.Func 和 'System.Func 类型定义二元运算符 OrElse。

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

【问题讨论】:

  • 也许用Expression.MakeBinary(ExpressionType.OrElse, exp, exp)替换三元组中的exp
  • @Auditive - 我之前也尝试过类似的东西,我已经相应地更新了问题,但是我得到了错误:二进制运算符 OrElse 没有为 System.Func.
  • 这能回答你的问题吗? How to append expressions in linq?

标签: c# expression expression-trees


【解决方案1】:

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

Expression 继承自 LambdaExpression,它表示 C#(或 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 属性,该属性从对应于 this 的表达式中提取:

(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。


可以解决这个问题,但我建议首先使用 System.Linq.Expressions.Expression 中的工厂方法将 SelectionCriteria 中的每个元素映射到与标准评估对应的表达式。然后,您可以将这些表达式组合成一个 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));

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-06-01
    • 1970-01-01
    • 1970-01-01
    • 2019-11-26
    • 1970-01-01
    • 1970-01-01
    • 2021-06-17
    相关资源
    最近更新 更多