Having trouble Aggregating over a list of expressions with Expression.AndAlso
c#, expression, lambda, linq
Solution
The problem that you have here is that you have functions (well, technically, expressions representing functions) that you need to combine. `AndAlso` doesn't really make sense when called on two functions; it needs to be called on two expression that resolve directly to boolean values. You need to get the body of each of those functions, rather than the whole function, and `AndAlso` those bodies together.
But just grabbing the bodies isn't enough; if you do that you'll have the problem that each body's parameter is different. You need to replace all uses of each function's parameter with the new parameter that you're creating for your resulting function.
To handle replacing those parameters we can use the following helper class, and a helper method that calls it, to do the replacing:
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);
}
Given this helper method, you've done most all of the work; there's not much left:
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);
}
Of course, another route is to create a `PredicateBuilder` class that can `And` or `Or` any two functions, each taking a common type and returning a boolean. They're each slightly simpler than your example:
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);
}
}
If we take the time to make this reusable type first, then generalizing it from two functions to N is pretty trivial:
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));
}
Problem
I'm new with explicitly building LINQ Expressions, and I'm trying to figure out how to combine a IEnumerable>> into a single Expression> by using Aggregate and Expression.AndAlso. I feel like I'm getting close, but I'm obviously missing something. ``` 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); } ``` When I call this method, I get the following exception: ``` System.ArgumentException: Expression of type 'System.Func`2[SomeEntity,System.Boolean]' cannot be used for return type 'System.Boolean' ``` Please help!