c# - 使用 Expression.AndAlso 聚合表达式列表时遇到问题

标签 c# linq lambda expression

我是显式构建 LINQ 表达式的新手,我正在尝试弄清楚如何使用 Aggregate 和 Expression.AndAlso 将 IEnumerable>> 组合成单个 Expression>。

我觉得我越来越接近了,但我显然错过了什么。

public static Expression<Func<T, bool>> CombineExpressions<T>(
IEnumerable<Expression<Func<T, bool>>> expressions)
{

  if (expressions == null || expressions.Count() == 0)
  {
    return t => true;
  }
  var combined = expressions
                .Cast<Expression>()
                .Aggregate((a, b) => Expression.AndAlso(a, b));

  ParameterExpression pe = Expression.Parameter(typeof(T), "x");

  return Expression.Lambda<Func<T, bool>>(combined, pe);
}

当我调用此方法时,出现以下异常:

System.ArgumentException: 
       Expression of type 'System.Func`2[SomeEntity,System.Boolean]'
       cannot be used for return type 'System.Boolean'

请帮忙!

最佳答案

这里的问题是您有需要组合的函数(嗯,从技术上讲,是表示函数的表达式)。 AndAlso 在调用两个函数时并没有真正的意义;需要在两个直接解析为 bool 值的表达式上调用它。您需要获取每个函数的主体,而不是整个函数,并将这些主体 AndAlso 放在一起。

但是仅仅捕获尸体是不够的;如果你这样做,你会遇到每个 body 的参数不同的问题。您需要将每个函数参数的所有使用替换为您为生成的函数创建的新参数。

为了处理这些参数的替换,我们可以使用以下辅助类和调用它的辅助方法来进行替换:

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

有了这个辅助方法,您就完成了大部分工作;所剩无几:

public static Expression<Func<T, bool>> CombineExpressions<T>(
    IEnumerable<Expression<Func<T, bool>>> expressions)
{
    if (expressions == null || expressions.Count() == 0)
    {
        return t => true;
    }
    ParameterExpression param = Expression.Parameter(typeof(T), "x");
    var combined = expressions
                    .Select(func => func.Body.Replace(func.Parameters[0], param))
                    .Aggregate((a, b) => Expression.AndAlso(a, b));

    return Expression.Lambda<Func<T, bool>>(combined, param);
}

当然,另一种方法是创建一个 PredicateBuilder 类,它可以AndOr 任意两个函数,每个函数都采用通用类型和返回一个 bool 值。它们都比您的示例稍微简单一些:

public static class PredicateBuilder
{
    public static Expression<Func<T, bool>> True<T>() { return f => true; }
    public static Expression<Func<T, bool>> False<T>() { return f => false; }

    public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> expr1,
                                                        Expression<Func<T, bool>> expr2)
    {
        var secondBody = expr2.Replace(expr2.Parameters[0], expr1.Parameters[0]);
        return Expression.Lambda<Func<T, bool>>
              (Expression.OrElse(expr1.Body, secondBody), expr1.Parameters);
    }

    public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> expr1,
                                                         Expression<Func<T, bool>> expr2)
    {
        var secondBody = expr2.Replace(expr2.Parameters[0], expr1.Parameters[0]);
        return Expression.Lambda<Func<T, bool>>
              (Expression.AndAlso(expr1.Body, secondBody), expr1.Parameters);
    }
}

如果我们先花时间制作这个可重用类型,那么将它从两个函数推广到 N 就非常简单了:

public static Expression<Func<T, bool>> CombineExpressions<T>(
    IEnumerable<Expression<Func<T, bool>>> expressions)
{
    if (expressions == null || expressions.Count() == 0)
    {
        return t => true;
    }
    return expressions.Aggregate((a, b) => a.And(b));
}

关于c# - 使用 Expression.AndAlso 聚合表达式列表时遇到问题,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/20380078/

相关文章:

c# - 为什么相机永远不会面向下一个目标旋转?

c# - 代码页问题

c# - 如何将略有不同的团队名称匹配到一个团队中

c# - 使用 Lambda 或 Linq 为 MVC5 中的 Razor View 创建带有所选项目的 SelectList 模型

java - 按字母顺序对集合进行排序,集合中的字母以逗号分隔

c# - 为什么我不能访问 DelegateCommand 的执行委托(delegate)中的实例属性?

c# - 如何在 32 位或 64 位以及任何操作系统语言的 XP、Vista 和 7 中获取所有用户帐户名

c# - 帮助理解 Enumerable.Join 方法

linq - 如何使用 LINQ 执行此使用列重命名(或列别名)的 SELECT 语句?

JavaFX ListView lambda