Linq Expressions By Sample: Calling Generic Methods With Lambda Parameters For Unknown Types

Suppose you need to call a generic Method that receives a generic delegate parameter. Now suppose that you neither know the type of the generic argument for the method nor the implementation of the delegate parameter at compile time. You could build a Linq Expression at runtime to solve this problem. Here’s how.

At Compile Time

Consider the following code. It contains a class “Executer” that has a method “Do” with a generic delegate as a parameter. Also, the code contains a class “Model” that will be passed to “Do”. Inside “Do”, “Model”‘s property “Numbers” will be returned from the generic delegate parameter and written to the console. In this code, the call to “Do” is as you would write it when you know the “Model” and “Numbers” at compile time:

class Model
{
    public IEnumerable<int> Numbers { get; set; }

    public Model()
    {
        Numbers = new[] { 1, 2, 3, 4 };
    }
}

class Executer
{
    public void Do<T1, T2>(T1 x, Func<T1, IEnumerable<T2>> f)
    {
        var l = f(x);

        foreach (var y in l)
        {
            Console.WriteLine(y);
        }
    }
}

class Program
{
    static void Test1(Executer executer, Model model)
    {
        executer.Do(model, m => m.Numbers);
    }
}

At Runtime With Linq Expressions

Now, if the real type of the “model” variable was missing at compile time, the code above will not be possible. Instead, we’ll need to re-construct the code from above using Linq Expressions and the type information available at runtime, generate a method that contains that code, compile the method and execute it.

class Program
{
    static void Test2(Executer executer, object model, string property)
    {
        var modelType = model.GetType();
        var modelProperty = modelType.GetProperty(property);
        var calleeType = executer.GetType();
        var method = calleeType.GetMethod("Do").MakeGenericMethod(modelType, modelProperty.PropertyType.GetGenericArguments().First());

        // executer
        var executerParameter = Expression.Parameter(calleeType, "executer");

        // x
        var modelParameter = Expression.Parameter(modelType, "x");

        // y => y.Property
        var innerParameter = Expression.Parameter(modelType, "y");
        var lambdaParameter = Expression.Lambda(Expression.Property(innerParameter, modelProperty), innerParameter);

        // Do(x, y => y.Numbers)
        var call = Expression.Call(executerParameter, method, modelParameter, lambdaParameter);

        // f = (executer, x) => executer.Do(x, y => y.Numbers)
        var lambda = Expression.Lambda(call, executerParameter, modelParameter);

        // f(executer, x)
        lambda.Compile().DynamicInvoke(executer, model);
    }
}

Too Slow

Of course, the construction and compilation of Linq Expressions is not really a fast way to call methods. To show how slow this is, I commented out the “Console.Wirteline” line inside “Do” and called both methods “Test1” and “Test2” as in the following code. The execution time for the compile-time implementation (“Test1”) is 2 ms and the time for the runtime implementation (“Test2”) is 6875 ms. Runtime-construction and compilation of Linq Expressions here is over 3400 times slower than a regular method call!

class Program
{
    static void Main()
    {
        var model = new Model();
        var executer = new Executer();
        const int n = 10000;
        var sw = Stopwatch.StartNew();

        for (var i = 0; i < n; i++)
        {
            Test1(executer, model);
        }

        Debug.WriteLine(sw.ElapsedMilliseconds);

        sw = Stopwatch.StartNew();

        for (var i = 0; i < n; i++)
        {
            Test2(executer, model, "Numbers");
        }

        Debug.WriteLine(sw.ElapsedMilliseconds);
    }

    // ...
}

Pre-Compilation To The Rescue

Fear not! If you have a reason to assume that your will be using the arguments passed to “Test2” more than once, you can pre-compile the constructed expression and re-use it over and over. For that, slight modifications to “Test2” from above will be needed. “Test2” will now return a compile-time known delegate that accepts the model as an “object” parameters at compile-time. The retruned implementation of this delegate converts the “object” into the type of the model and construct the call to “Do”.

static Action<Executer, object> Test2(Executer executer, object model, string property)
{
    var modelType = model.GetType();
    var modelProperty = modelType.GetProperty(property);
    var calleeType = executer.GetType();
    var method = calleeType.GetMethod("Do").MakeGenericMethod(modelType, modelProperty.PropertyType.GetGenericArguments().First());

    // executer
    var executerParameter = Expression.Parameter(calleeType, "executer");

    // (T)x
    var modelParameter = Expression.Parameter(typeof(object), "x");
    var modelConversion = Expression.Convert(modelParameter, modelType);

    // y => y.Property
    var innerParameter = Expression.Parameter(modelType, "y");
    var lambdaParameter = Expression.Lambda(Expression.Property(innerParameter, modelProperty), innerParameter);

    // executer.Do((T)x, y => y.Numbers)
    var call = Expression.Call(executerParameter, method, modelConversion, lambdaParameter);

    // f = (Executer executer, object x) => executer.Do((T)x, y => y.Numbers)
    var lambda = Expression.Lambda(call, executerParameter, modelParameter);

    // f(executer, x)
    var f = (Action<Executer, object>)lambda.Compile();
    return f;
}

The “Main” method is changed to the code below. The time measurement yields a time for construction and compilation of 20 ms and 36 ms for the 10000 iterations of the call to “Do”. The time to call “Do” is only 18 times slower than a regular call. Taking the time to create and compile the expression into account makes it only 28 times slower. While that is not incredibly fast at all, it is quite sufficient for most business cases, I think.

class Program
{
    static void Main()
    {
        var model = new Model();
        var executer = new Executer();
        const int n = 10000;

        var sw = Stopwatch.StartNew();

        // Create and compile Linq Expressions
        var f = Test2(executer, model, "Numbers");
            
        Debug.WriteLine(sw.ElapsedMilliseconds);

        sw = Stopwatch.StartNew();

        for (var i = 0; i < n; i++)
        {
            f(executer, model);
        }

        Debug.WriteLine(sw.ElapsedMilliseconds);
    }

    // ...
}

Freelance full-stack .NET and JS developer and architect. Located near Cologne, Germany.

7 thoughts on “Linq Expressions By Sample: Calling Generic Methods With Lambda Parameters For Unknown Types

      1. I tried that, too. But then it complained about invalid parameters. I’ve been trying to figure out how to attach the instance but no resolution yet..

  1. Update: the first Test2() works with
    var call = Expression.Call(Expression.Constant(executer), method, modelParameter, lambdaParameter);

    The compiled version doesn’t like that, either.

Leave a Reply

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