Expression Tree and Merge of Expressions

The Expression class in C# allows you to build computational expressions in run time. Most of examples you can find are showing you how to build a simple scenario of a binary expression, as a tree it will have many levels, then the question is how to combine expressions? Or how to merge expressions?

The merge of expressions means things one is how to merge body, the other is about merging of parameters, there are a few way of doing this, before we going into details of that , I would like to reiterate a few basic but important concepts in expression tree.

ExpressionTree

ExpressionTree

As you can see from graph, body and parameters are two important parts of an expression, but naming of these components all as XXXexpression is not helpful at all for us to differentiate which components are expression body and which ones are parameters. Clarifying them as Body and Parameters are important when you using Expression class to build a tree.

The other confusing aspect about Expression tree is how to execute them? basically you have to do two things:

1) wrap your expression body and parameters into a strongly typed lambda expression Expression where TDelegate is a Func<> generics

Or wrap your expression body and parameters into a none type lambda expression LambdaExpression

Actually Expression is inherited from LambdaExpression, so only LambdaExpression is executable, in other words to make an expression executable, you will have to wrap them into LambdaExpression.

2) second step is to compile the LambdaExpression into a function, as LambdaExpression is data, compiling it makes them executable code.

Here is the example of building and executing Expression of x=>x<5 in two ways both untyped and typed Lambda aka Expression

Declaring parameters

   ParameterExpression numParam = Expression.Parameter(typeof(int), "num");
   ConstantExpression five = Expression.Constant(5, typeof(int));

Well ConstanExpression is not exactly a parameter.

Declaring body

   BinaryExpression numLessThanFive = Expression.LessThan(numParam, five);

Then wrap it as a typed LambdaExpression and executes it

   // typed LambdaExpression
            Expression<Func<int, bool>> typedLambda =
                Expression.Lambda<Func<int, bool>>(
                    numLessThanFive,
                    new ParameterExpression[] { numParam });

            bool result1 = typedLambda.Compile()(6);

TDelegate is Func, and execution is simply call the compiled function with 6 and expecting a bool, nice and tidy

What about untyped LambdaExpression ?

   // another example , untyped LambdaExpression

            LambdaExpression untypedLambda =
                Expression.Lambda(
                    numLessThanFive,
                    new ParameterExpression[] { numParam });

            var result2 = typedLambda.Compile().DynamicInvoke(6);

So when it is untyped, it is simply LambdaExpression with no idea of input and output, as it is not typed, DynamicInvoke is used to execute the function, no design time check on parameters and return type.

It is possible that when you build expression tree, you do not know the number of parameters and their types, there is no way to know TDelegate(Func in example) before hand, the untyped LambdaExpression comes to rescure, in this case you can have any number of parameters.

Enough about the primers of Expression tree, coming back to the topic, I am here going to show you a few ways of merging expressions and their implications

The user case is how to build expression : (number1,number2, number3,bool)=> numer1+number2< number3, you can make it more complicated, the point here is how to take the first step of merging To break the expression down is we have first expression (number1,number2, int)=> numer1+number2 then combine it with third number using comparison operator <, you obvious can combine it with another expression, but it should not make too much of difference. It involves merging both Body and parameters Implementing it in typed LambdaExpression

  // Parameter for the lambda expression.
            ParameterExpression paramExpr1 = Expression.Parameter(typeof(int), "arg1");
            ParameterExpression paramExpr2 = Expression.Parameter(typeof(int), "arg2");

        
          
            LambdaExpression lambdaExpr1 = Expression.Lambda(
                Expression.Add(
                    paramExpr1,
                    paramExpr2
                ),
                new List<ParameterExpression>() { paramExpr1,paramExpr2 }
            );


            ParameterExpression paramExpr3 = Expression.Parameter(typeof(int), "arg3");

            BinaryExpression binaryExpression = Expression.LessThan(lambdaExpr1.Body, paramExpr3);
            
            List<ParameterExpression> mergedParameters = new List<ParameterExpression>();

            foreach (ParameterExpression p in lambdaExpr1.Parameters)
            {
                mergedParameters.Add(p);
            }

            mergedParameters.Add(paramExpr3);


            LambdaExpression mergedLambdaExpr = Expression.Lambda(
                binaryExpression,
                mergedParameters
            );

            var result = mergedLambdaExpr.Compile().DynamicInvoke(4, 5, 6);

Implementing it in untype LambdaExpression, the advantage of this way is parameter list is dynamic.

// Parameters for the lambda expression.
            ParameterExpression paramExpr1 = Expression.Parameter(typeof(int), "arg1");
            ParameterExpression paramExpr2 = Expression.Parameter(typeof(int), "arg2");

          
            Expression<Func<int, int, int>> lambdaExpr = Expression.Lambda<Func<int, int, int>>(
                Expression.Add(
                    paramExpr1,
                    paramExpr2
                ),
                new List<ParameterExpression>() { paramExpr1, paramExpr2 }
            );

            ParameterExpression paramExpr3 = Expression.Parameter(typeof(int), "arg3");

            BinaryExpression binaryExpression = Expression.LessThan(lambdaExpr.Body, paramExpr3);
            
            List<ParameterExpression> mergedParameters = new List<ParameterExpression>();

            foreach (ParameterExpression p in lambdaExpr.Parameters)
            {
                mergedParameters.Add(p);
            }

            mergedParameters.Add(paramExpr3);


            Expression<Func<int, int,int, bool>> mergedLambdaExpr =
                Expression.Lambda<Func<int, int,int, bool>>(
                binaryExpression,
                mergedParameters
            );


            bool result = mergedLambdaExpr.Compile()(4, 5, 6);

Above examples show parameters of expressions are simply added, so two parameters of first expression plus third parameter make 3 parameters for final expression.

There is a scenario that the parameters are shared, we see a lot of examples of this scenario from PredicatBuilder, that is if we combine (x=>x>5) and (x=>x<10) into (x => x>5 and x<10), this needs the parameter of expression 1 to be shared of expression 2 after merging, this is done through the use of InvocationExpression where only Body is kept but parameters are replaced.

  // A parameter for the lambda expression.
            ParameterExpression paramExpr1 = Expression.Parameter(typeof(int), "arg1");
         
                     
            // This expression represents a lambda expression 
            // that adds 1 to the parameter value.
            LambdaExpression lambdaExpr1 = Expression.Lambda(
                Expression.GreaterThan(
                    paramExpr1,
                    Expression.Constant(5, typeof(int))
                ),
                new List<ParameterExpression>() { paramExpr1}
            );

          //  (x=> x>5)
            ParameterExpression paramExpr2 = Expression.Parameter(typeof(int), "arg2");
         
            LambdaExpression lambdaExpr2 = Expression.Lambda(
                  Expression.LessThan(
                      paramExpr2,
                      Expression.Constant(10, typeof(int))
                  ),
                  new List<ParameterExpression>() { paramExpr2 }
              );

            //  (x=> x<10)

            InvocationExpression lambdaExpr1Invoke = Expression.Invoke(lambdaExpr1, lambdaExpr2.Parameters.Cast<Expression>());

            BinaryExpression binaryExpression = Expression.AndAlso(lambdaExpr1Invoke, lambdaExpr2.Body);

            LambdaExpression mergedExpression = Expression.Lambda(
                binaryExpression,
                new List<ParameterExpression>() { paramExpr2 }
            );

            var result = mergedExpression.Compile().DynamicInvoke(7);

This is very useful when you do build predicate, the parameter is always an object, the predicates testing different properties of an object are combined with And or OR operators to make complicated selections

This entry was posted on Friday, June 28th, 2013 at 4:03 am and is filed under ASP.NET. You can follow any responses to this entry through the RSS 2.0 feed. You can leave a response, or trackback from your own site.

Leave a Reply

*