How to create an Expression AND clause from two expressions
Rewriting expressions has been made easy with the addition of ExpressionVisitor to BCL. With some helpers the task gets almost trivial.
Here's a visitor class I use to apply a delegate to the tree nodes:
internal sealed class ExpressionDelegateVisitor : ExpressionVisitor {
private readonly Func<Expression , Expression> m_Visitor;
private readonly bool m_Recursive;
public static Expression Visit ( Expression exp , Func<Expression , Expression> visitor , bool recursive ) {
return new ExpressionDelegateVisitor ( visitor , recursive ).Visit ( exp );
}
private ExpressionDelegateVisitor ( Func<Expression , Expression> visitor , bool recursive ) {
if ( visitor == null ) throw new ArgumentNullException ( nameof(visitor) );
m_Visitor = visitor;
m_Recursive = recursive;
}
public override Expression Visit ( Expression node ) {
if ( m_Recursive ) {
return base.Visit ( m_Visitor ( node ) );
}
else {
var visited = m_Visitor ( node );
if ( visited == node ) return base.Visit ( visited );
return visited;
}
}
}
And here are the helper methods to simplify the rewriting:
public static class SystemLinqExpressionsExpressionExtensions {
public static Expression Visit ( this Expression self , Func<Expression , Expression> visitor , bool recursive = false ) {
return ExpressionDelegateVisitor.Visit ( self , visitor , recursive );
}
public static Expression Replace ( this Expression self , Expression source , Expression target ) {
return self.Visit ( x => x == source ? target : x );
}
public static Expression<Func<T , bool>> CombineAnd<T> ( this Expression<Func<T , bool>> self , Expression<Func<T , bool>> other ) {
var parameter = Expression.Parameter ( typeof ( T ) , "a" );
return Expression.Lambda<Func<T , bool>> (
Expression.AndAlso (
self.Body.Replace ( self.Parameters[0] , parameter ) ,
other.Body.Replace ( other.Parameters[0] , parameter )
) ,
parameter
);
}
}
Which allows to combine the expressions like this:
static void Main () {
Expression<Func<int , bool>> leftExp = a => a > 3;
Expression<Func<int , bool>> rightExp = a => a < 7;
var andExp = leftExp.CombineAnd ( rightExp );
}
UPDATE:
In case ExpressionVisitor
's not available, its source had been published a while ago here. Our library used that implementation until we've migrated to .NET 4.
You cannot do that without rewriting both complete expression trees into a complete new one.
Reason: the parameter-expression objects must be the same for the whole expression tree. If you combine the two, you have two parameter-expression objects for the same parameter, which will not work.
It shows with the following code:
Expression<Func<Tab, bool>> leftexp = tag => ((tag.id == 2) || (tag.id == 3));
Expression<Func<Tab, bool>> rightexp = tag => ((tag.uid == "MU") || (tag.uid == "ST"));
Expression binaryexp = Expression.AndAlso(leftexp.Body, rightexp.Body);
ParameterExpression[] parameters = new ParameterExpression[1] {
Expression.Parameter(typeof(Tab), leftexp.Parameters.First().Name)
};
Expression<Func<Tab, bool>> lambdaExp = Expression.Lambda<Func<Tab, bool>>(binaryexp, parameters);
var lambda = lambdaExp.Compile();
This fails on the lambdaExp.Compile() call, which gives the following exception:
Lambda Parameter not in scope
This is caused by the fact that basically I'm re-using the leftexp and rightexp expression, but they have different parameter-expressions, both which are not given by me to the Expression.Lambda<Func<Tab>>(...)
call. Deep down into the leftexp and rightexp there are parameter-expression objects which must match the one given to the Expression.Lambda<Func<Tab>>(...)
call.
To solve this you have recreate the complete expression using a new (single) parameter-expression for parameter tag.
See here for more information about the problem.