我是显式构建 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
类,它可以And
或 Or
任意两个函数,每个函数都采用通用类型和返回一个 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/