Saturday, January 23, 2010

Runtime evaluation of lambda expressions

During working over Camlex.NET project in order to provide support of non-constant expressions we needed evaluate sub-expressions in runtime as results of these expression are required to construct CAML query. I.e. suppose that we have expression of the following general schema:

   1: x => (Type)x[expr1] == expr2

Here x is a parameter of lambda expression, Type – type allowed for casting in Camlex, and expr1 and expr2 – some expressions which should be evaluated in runtime. Expr1 and expr2 can be for example variables, method calls, ternary operator, etc.. For example:

   1: Func<object> expr1 = () => "Title";
   2: Func<object> expr2 = () => "Camlex";
   3:  
   4: var caml =
   5:     Camlex.Query()
   6:         .Where(x => (string)x[(string)expr1()] == expr2()).ToString();

The expression above will be translated in the following CAML:

   1: <Where>
   2:   <Eq>
   3:     <FieldRef Name="Title" />
   4:     <Value Type="Text">Camlex</Value>
   5:   </Eq>
   6: </Where>

As shown here results of expr1 and expr2 (“Title” and “Camlex”) are used for CAML query creation. So how do we evaluate exp1 and expr2:

   1:  
   2: private object evaluateExpression(Expression expr)
   3: {
   4:     // need to add Expression.Convert(..) in order to define Func<object>
   5:     var lambda =
   6: Expression.Lambda<Func<object>>(Expression.Convert(expr, typeof(object)));
   7:     return lambda.Compile().Invoke();
   8: }

First of all we need to represent expr1 and expr2 as Expression object using expression trees. In order to evaluate this expression we need to convert it to lambda expression () => expr which returns “object” type. Here little trick is made: we construct new unary expression which represents casting to object:

   1: Expression.Convert(expr, typeof(object))

Whatever actual expression was in expr1 or expr2, now the final operation for these expressions will be casting to object: (object)expr1 (of course it is not so efficiency if expressions return value type because of boxing/unboxing, but it will allow us to invoke expression in runtime). I.e. we added new operation to existing expressions.

Now it is not so hard to evaluate this expression. We create lambda expression: () => (object)expr1 with the following code:

   1:  
   2: var lambda =
   3: Expression.Lambda<Func<object>>(Expression.Convert(expr, typeof(object)));
And then we call Compile()  and Invoke() methods to get results. After that these results are used to create CAML query.

No comments:

Post a Comment