您在这里遇到的问题是您有需要组合的函数(从技术上讲,是表示函数的表达式)。 AndAlso 在两个函数上调用时没有任何意义;它需要在两个直接解析为布尔值的表达式上调用。您需要获取每个函数的 body,而不是整个函数,并将 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 任意两个函数,每个函数采用一个通用类型并返回一个布尔值。它们都比您的示例稍微简单一些:
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));
}