Search code examples
c#lambdalinq-expressions

Pre compiled lambda expression to create class that has a constructor with a parameter


I am currently using Activator.CreateInstance to create an instance of class whose type is passed in as a generic parameter. The problem is, this is awfully slow. I read somewhere that I could do the same thing using a pre compiled lambda expression, however, I am having trouble implementing this in my case due to a parameter needing to be passed to the instance of the class being created.

Currently I am doing the following

public class Class1
{
    private int TestVariable;

    public Class1(int testVariable)
    {
        TestVariable = testVariable;
    }

    public void TestMethod()
    {
        Console.WriteLine($"Test Variable was {TestVariable}");
    }
}

public object Test<T>(params object[] parameters)
{
    var instance = (T) Activator.CreateInstance(typeof(T), BindingFlags.Instance, null, new object[] {9999}, null);

    var testMethod = typeof(T).GetMethod("TestMethod", BindingFlags.Instance);

    return testMethod.Invoke(instance, parameters)
}

How could I accomplish something similar to this using a pre compiled lambda expression?


Solution

  • Actually, using Expression.Compile is going to be slower if you need to create only one object. However if you cache the creator function, it's going to be faster in the long run, as lambda performance will be good.

    Here is the code:

    static Func<TArg, T> CreateCreator<TArg, T>()
    {
        var constructor = typeof(T).GetConstructor(new Type[] { typeof(TArg) });
        var parameter = Expression.Parameter(typeof(TArg), "p");
        var creatorExpression = Expression.Lambda<Func<TArg, T>>(
            Expression.New(constructor, new Expression[] { parameter }), parameter);
        return creatorExpression.Compile();
    }
    
    Func<TArg, T> creator = CreateCreator<TArg, T>();
    

    Having this, you can use creator(arg) for creating new objects of T.


    Small benchmark of the three ways:

    class Program
    {
        static void Main(string[] args)
        {
            // warm up
            Test1<Class1>(0);
            Test2<int, Class1>(0);
    
            const int numiter = 10000;
            var sw1 = Stopwatch.StartNew();
            for (int i = 0; i < numiter; i++)
                Test1<Class1>(i);
            sw1.Stop();
            Console.WriteLine($"With Activator.CreateInstance: " +
                $"{(double)sw1.ElapsedTicks / numiter} ticks per object");
    
            var sw2 = Stopwatch.StartNew();
            for (int i = 0; i < numiter; i++)
                Test2<int, Class1>(i);
            sw2.Stop();
    
            Console.WriteLine($"With Expression.Compile: " +
                $"{(double)sw2.ElapsedTicks / numiter} ticks per object");
    
            var sw3 = Stopwatch.StartNew();
            var creator = CreateCreator<int, Class1>();
            for (int i = 0; i < numiter; i++)
                creator(i);
            sw3.Stop();
    
            Console.WriteLine($"With cached Expression.Compile: " +
                $"{(double)sw3.ElapsedTicks / numiter} ticks per object");
        }
    
        static public object Test1<T>(params object[] parameters)
        {
            var instance = (T)Activator.CreateInstance(
                typeof(T), BindingFlags.Instance | BindingFlags.Public, null, parameters, null);
            return instance;
        }
    
        static Func<TArg, T> CreateCreator<TArg, T>()
        {
            var constructor = typeof(T).GetConstructor(new Type[] { typeof(TArg) });
            var parameter = Expression.Parameter(typeof(TArg), "p");
            var creatorExpression = Expression.Lambda<Func<TArg, T>>(
                Expression.New(constructor, new Expression[] { parameter }), parameter);
            return creatorExpression.Compile();
        }
    
        static public object Test2<TArg, T>(TArg arg)
        {
            var creator = CreateCreator<TArg, T>();
            return creator(arg);
        }
    }
    

    produces the following result on my machine (release mode/outside Visual Studio):

    With Activator.CreateInstance: 3.0739 ticks per object
    With Expression.Compile: 494.0388 ticks per object
    With cached Expression.Compile: 0.1097 ticks per object