Dynamically Generating Lambdas

Writing

In one of my previous posts I talked about chaining Linq queries and how you can use this to form queries with optional predicates in them. It really didn’t let you build truly dynamic Linq expressions, but it is a good step in that direction. I started thinking about how I could make this into a system to truly build up dynamic Linq queries, but I got distracted when I started thinking about building lambdas dynamically. So, I decided to look into it a bit.

First I started off with an extremely simple method that looked like this:

  public static Func<T, T, T> GenerateAddLambda<T>()
  {
    ParameterExpression param1 = 
      Expression.Parameter(typeof(T), "arg1");
    ParameterExpression param2 = 
      Expression.Parameter(typeof(T), "arg2");
 
    Expression expr = Expression.Add(param1, param2);
 
    var lambda = 
      Expression.Lambda<Func<T, T, T>>(expr, param1, param2);
    return lambda.Compile();
  }

This method has no parmeters, but it does have one type parameter. You simply have to tell it what type that the add statement is going to operate on. First off we create two ParameterExpression objects and give them random names, in this case "arg1" and "arg2". I then create an add BinaryExpression object by calling "Expression.Add". Now that we have our expression we simply create a LambdaExpression and pass our expression into it along with the two parameters that we created. Then we return the results of LambdaExpression.Compile, which is where the magic happens. The Compile method creates a new instance of the ExpressionCompiler (an internal class) class and calls Compile on it. This actually generates our lambda from the expression.

In order for us to consume this method, we would call it like this:

Func<int,int,int> addLambda = GenerateAddLambda<int>();
 
int result = addLambda(5, 6);

I spelled out the type of the lambda created by "GenerateAddLambda" so that you could see the type that is returned. Normally I would just use "var" so that I wouldn’t have to see this. But as you can see we call the method, get our lambda that we can then call normally to get our result. Not bad for a first step, but at this point there is absolutely no point in generating this lambda when we can just type it in.

So, the next step is to allow us to perform different operations. For now I am just going to make a "GenerateBinaryLambda" method that will generate a method that takes two parameters. This method will use this enumeration:

  public enum BinaryExpressionType
  {
    Add = 0,
    Subtract = 1,
    Concat = 2
  }

This will allow us to specify which operation we want to perform on our two parameters. So, we are going to do almost exactly what we did in the first method, but expand it to create more than one type of lambda. Our method ends up looking like this:

  public static Func<T, T, T>
      GenerateBinaryLambda<T>(BinaryExpressionType expression)
  {
    ParameterExpression param1 = 
      Expression.Parameter(typeof(T), "arg1");
    ParameterExpression param2 = 
      Expression.Parameter(typeof(T), "arg2");
 
    Expression expr = null;
    switch (expression)
    {
      case BinaryExpressionType.Add:
        expr = Expression.Add(param1, param2);
        break;
      case BinaryExpressionType.Subtract:
        expr = Expression.Subtract(param1, param2);
        break;
      case BinaryExpressionType.Concat:
        Type[] types = { typeof(T), typeof(T) };
        System.Reflection.MethodInfo memberInfo =
          typeof(String).GetMethod("Concat", types);
        expr = Expression.Call(memberInfo, param1, param2);
        break;
    }
 
    var lambda = 
      Expression.Lambda<Func<T, T, T>>(expr, param1, param2);
    return lambda.Compile();
  }

The only real difference here is our switch statement that generates different expression types that we use in our lambda. Now, this example gets a little more useful but you definitely want to think twice before you do this since you are opening yourself up to all kinds of runtime errors. For example, you could call it like this:

var concatLambda = 
  GenerateBinaryLambda<int>(BinaryExpressionType.Concat);
int result = concatLambda(5, 2);

The compiler would be happy to compile this, but you would end up with runtime errors telling you that you cannot call String.Concat with System.Int32. Obviously.

So now lets move onto something a bit more difficult. What if you wanted to generate a lambda, but you didn’t know at compile time how many parameters you would have? How would you generate this and how would then call it? While not quite as easy as what we have done above, Microsoft has still made it pretty darn easy for us to do this.

Lets first think about exactly what problem we are trying to solve. We want to take an operation like "Add" or "Subtract" and pass a variable number of arguments to it. So, for addition, the first iteration would be (n1 + n2) and then the second would be ((n1 + n2) + n3). So we must construct our expression in this way, so that after the first iteration the subsequent iterations will use the previous expression as one of the parameters.

I ended up producing a method that looks like this (I make no claims about the quality of this code, it is only meant as a technical demonstration. And yes, before someone says something, you would want to move the MethodInfo declaration out of the method 🙂 ):

  public static Delegate GenerateLambda<T>
    (int numArgs, BinaryExpressionType expressionType)
  {
    Expression param1 = null;
    Expression param2 = null;
    List<ParameterExpression> parameters = 
      new List<ParameterExpression>();
 
    Type[] types = { typeof(T), typeof(T) };
    System.Reflection.MethodInfo memberInfo =
      typeof(String).GetMethod("Concat", types);
 
    for (int i = 0; i < numArgs; i++)
    {
      if (param1 == null)
      {
        param1 = Expression.Parameter(typeof(T), "arg" + i);
        parameters.Add((ParameterExpression)param1);
      }
      else
      {
        param2 = Expression.Parameter(typeof(T), "arg" + i);
        parameters.Add((ParameterExpression)param2);
      }
 
      if (param2 != null)
      {
        switch (expressionType)
        {
          case BinaryExpressionType.Add:
            param1 = Expression.Add(param1, param2);          
            break;
          case BinaryExpressionType.Subtract:
            param1 = Expression.Subtract(param1, param2);
            break;
          case BinaryExpressionType.Concat:
            param1 = Expression.Call(memberInfo, param1, param2);
            break;
        }
      }
    }
 
    var lambda = Expression
      .Lambda(param1, parameters.ToArray());
    return lambda.Compile();
  }

This method takes two parameters, one is an int which tells it the number of parameters we are going to have and the second is the same enumeration that I am using above. In the first pass we set param1 and in the second pass we set param2 and then we reset param1 to the Expression created from Param1 and Param2. Fairly simple, but you will notice now that we are now just returning a Delegate type instead of the pervious "Func<T, T, T>". So, we are going to create our Delegate like this:

  int[] values = { 4, 5, 4, 9 };
  Delegate lambda = 
    GenerateLambda<int>(values.Length, BinaryExpressionType.Add);

The issue now is that the lambda we are returning from GenerateLambda is wrapped in a Delegate object. This is the best we can do here since we don’t know how many parameters the lambda is going to have. But then how do we call this? Well, the Delegate class has a method called "DynamicInvoke", and it is our friend. DynamicInvoke’s signature looks like this:

  public object DynamicInvoke(params object[] args);

So you may think that all you have to do is call it like this:

  int[] values = { 4, 5, 4, 9 };
  Delegate lambda = 
    GenerateLambda<int>(values.Length, BinaryExpressionType.Add);
 
  int result = (int)lambda.DynamicInvoke(values);

But, as always, you’d be wrong. The compiler is more than happy to compile this, but since int[] is not convertable to object[] (the .net framework is very stubborn when it comes to casting value type arrays. It essentially says "I don’t wanna.") we just end up with an object array whose first parameter is an array of integers. As you probably suspect we get a runtime error telling us that the number of parameters do not match.

In order to fix this we have two options. For one we can just declare our "values" array as object[] instead of int[], this would allow us to call the method in this manner. Or we can use "Array.Copy" in order to copy our items into a new array. You will have to use one or the other based on your own situation (and there are obviously other ways to do this as well). If you are using a List, Collection or other any other class that supports ICollection object there is a ToArray() method, but it is going to create an array of the same type as your collection. That doesn’t really help us very much. You can always define an extension method like this though in order to allow an easy conversion:

  public static object[] ToObjectArray(this ICollection collection)
  {
    object[] array = new object[collection.Count];
    collection.CopyTo(array, 0);
    return array;
  }

But I am going to stop there for now, I don’t have much else to add at this point and I think you have a decent idea of how very simple lambdas are generated and then called. I have only covered a very small subset of the types of Expressions that you could create, but you can always play with it a bit more to fit it into your situation.

I hope that you enjoyed this post and while most of this is interesting in a technical way, you should always respect the KISS principle and seriously think about whether there are more simple and straightforward ways to accomplish something rather than dynamically generating lambdas. Unless of course you are a crazy l33t H4X0R.

Loved the article? Hated it? Didn’t even read it?

We’d love to hear from you.

Reach Out

Comments (3)

  1. Cool article! Have you seen Raj Kaimal’s page about the dynamic string to expression class? (Dynamic.cs; part of the VS2008 samples). I’ve used it successfully in a couple of projects; with one small change it can become ‘happy’ with any datatype you throw at it :o)

Leave a comment

Leave a Reply

Your email address will not be published. Required fields are marked *

More Insights

View All