> 到单个表达式>,c#,expression,expression-trees"/>

首页 > 解决方案 > 如何组合表达式 Expression> 到单个表达式>

问题描述

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

   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<Func<TConfiguration, bool>> 和二进制表达式之间没有隐式转换。

如果我使用

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

我明白了

没有为类型 'System.Func<TNumbering,TConfiguration,System.Boolean> 和 'System.Func<TNumbering,TConfiguration,System.Boolean> 定义二元运算符 OrElse。

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

标签: c#expressionexpression-trees

解决方案


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

Expression<TDelegate>继承自LambdaExpression,它表示一个 C#(或 VB.NET)lambda 表达式

就像您无法编写以下代码一样:

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

尝试将两个LambdaExpression组合在一起OrElse会在运行时引发异常。

您的代码甚至没有编译,因为expression类型为BinaryExpression,表示与此相对应的表达式:

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

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


每个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。


It's possible to work around this problem, but I would suggest first and foremost you map each element in SelectionCriteria to an expression corresponding to the criteria evaluation, using the factory methods at System.Linq.Expressions.Expression. You could then combine those expressions into a BinaryExpression which you could then wrap up in a LambdaExpression or even an Expression.

It might look something like this (making some assumptions):

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

推荐阅读