Activator.CreateInstance Performance Alternative

41,922

Solution 1

Use a compiled lambda if you can, its MUCH faster.

https://vagifabilov.wordpress.com/2010/04/02/dont-use-activator-createinstance-or-constructorinfo-invoke-use-compiled-lambda-expressions/

Solution 2

Don't forget about DynamicMethod

Here's example how to create new instance thru default constructor

public static ObjectActivator CreateCtor(Type type)
{
    if (type == null)
    {
        throw new NullReferenceException("type");
    }
    ConstructorInfo emptyConstructor = type.GetConstructor(Type.EmptyTypes);
    var dynamicMethod = new DynamicMethod("CreateInstance", type, Type.EmptyTypes, true);
    ILGenerator ilGenerator = dynamicMethod.GetILGenerator();
    ilGenerator.Emit(OpCodes.Nop);
    ilGenerator.Emit(OpCodes.Newobj, emptyConstructor);
    ilGenerator.Emit(OpCodes.Ret);
    return (ObjectActivator)dynamicMethod.CreateDelegate(typeof(ObjectActivator));
}

public delegate object ObjectActivator();

here's more about performance comparison

Measuring InvokeMember... 1000000 iterations in 1.5643784 seconds.

Measuring MethodInfo.Invoke... 1000000 iterations in 0.8150111 seconds.

Measuring DynamicMethod... 1000000 iterations in 0.0330202 seconds.

Measuring direct call... 1000000 iterations in 0.0136752 seconds.

Solution 3

I have created a solution that can be used as a drop in replacement for Activator.CreateInstance. You can find it on my blog.

Example:

var myInstance = InstanceFactory.CreateInstance(typeof(MyClass));
var myArray1 = InstanceFactory.CreateInstance(typeof(int[]), 1024);
var myArray2 = InstanceFactory.CreateInstance(typeof(int[]), new object[] { 1024 });

Code:

public static class InstanceFactory
{
  private delegate object CreateDelegate(Type type, object arg1, object arg2, object arg3);

  private static ConcurrentDictionary<Tuple<Type, Type, Type, Type>, CreateDelegate> cachedFuncs = new ConcurrentDictionary<Tuple<Type, Type, Type, Type>, CreateDelegate>();

  public static object CreateInstance(Type type)
  {
    return InstanceFactoryGeneric<TypeToIgnore, TypeToIgnore, TypeToIgnore>.CreateInstance(type, null, null, null);
  }

  public static object CreateInstance<TArg1>(Type type, TArg1 arg1)
  {
    return InstanceFactoryGeneric<TArg1, TypeToIgnore, TypeToIgnore>.CreateInstance(type, arg1, null, null);
  }

  public static object CreateInstance<TArg1, TArg2>(Type type, TArg1 arg1, TArg2 arg2)
  {
    return InstanceFactoryGeneric<TArg1, TArg2, TypeToIgnore>.CreateInstance(type, arg1, arg2, null);
  }

  public static object CreateInstance<TArg1, TArg2, TArg3>(Type type, TArg1 arg1, TArg2 arg2, TArg3 arg3)
  {
    return InstanceFactoryGeneric<TArg1, TArg2, TArg3>.CreateInstance(type, arg1, arg2, arg3);
  }

  public static object CreateInstance(Type type, params object[] args)
  {
    if (args == null)
      return CreateInstance(type);

    if (args.Length > 3 || 
      (args.Length > 0 && args[0] == null) ||
      (args.Length > 1 && args[1] == null) ||
      (args.Length > 2 && args[2] == null))
    {
        return Activator.CreateInstance(type, args);   
    }

    var arg0 = args.Length > 0 ? args[0] : null;
    var arg1 = args.Length > 1 ? args[1] : null;
    var arg2 = args.Length > 2 ? args[2] : null;

    var key = Tuple.Create(
      type,
      arg0?.GetType() ?? typeof(TypeToIgnore),
      arg1?.GetType() ?? typeof(TypeToIgnore),
      arg2?.GetType() ?? typeof(TypeToIgnore));

    if (cachedFuncs.TryGetValue(key, out CreateDelegate func))
      return func(type, arg0, arg1, arg2);
    else
      return CacheFunc(key)(type, arg0, arg1, arg2);
  }

  private static CreateDelegate CacheFunc(Tuple<Type, Type, Type, Type> key)
  {
    var types = new Type[] { key.Item1, key.Item2, key.Item3, key.Item4 };
    var method = typeof(InstanceFactory).GetMethods()
                                        .Where(m => m.Name == "CreateInstance")
                                        .Where(m => m.GetParameters().Count() == 4).Single();
    var generic = method.MakeGenericMethod(new Type[] { key.Item2, key.Item3, key.Item4 });

    var paramExpr = new List<ParameterExpression>();
    paramExpr.Add(Expression.Parameter(typeof(Type)));
    for (int i = 0; i < 3; i++)
      paramExpr.Add(Expression.Parameter(typeof(object)));

    var callParamExpr = new List<Expression>();
    callParamExpr.Add(paramExpr[0]);
    for (int i = 1; i < 4; i++)
      callParamExpr.Add(Expression.Convert(paramExpr[i], types[i]));

    var callExpr = Expression.Call(generic, callParamExpr);
    var lambdaExpr = Expression.Lambda<CreateDelegate>(callExpr, paramExpr);
    var func = lambdaExpr.Compile();
    cachedFuncs.TryAdd(key, func);
    return func;
  }
}

public static class InstanceFactoryGeneric<TArg1, TArg2, TArg3>
{
  private static ConcurrentDictionary<Type, Func<TArg1, TArg2, TArg3, object>> cachedFuncs = new ConcurrentDictionary<Type, Func<TArg1, TArg2, TArg3, object>>();

  public static object CreateInstance(Type type, TArg1 arg1, TArg2 arg2, TArg3 arg3)
  {
    if (cachedFuncs.TryGetValue(type, out Func<TArg1, TArg2, TArg3, object> func))
      return func(arg1, arg2, arg3);
    else
      return CacheFunc(type, arg1, arg2, arg3)(arg1, arg2, arg3);
  }

  private static Func<TArg1, TArg2, TArg3, object> CacheFunc(Type type, TArg1 arg1, TArg2 arg2, TArg3 arg3)
  {
    var constructorTypes = new List<Type>();
    if (typeof(TArg1) != typeof(TypeToIgnore))
      constructorTypes.Add(typeof(TArg1));
    if (typeof(TArg2) != typeof(TypeToIgnore))
      constructorTypes.Add(typeof(TArg2));
    if (typeof(TArg3) != typeof(TypeToIgnore))
      constructorTypes.Add(typeof(TArg3));

    var parameters = new List<ParameterExpression>()
    {
      Expression.Parameter(typeof(TArg1)),
      Expression.Parameter(typeof(TArg2)),
      Expression.Parameter(typeof(TArg3)),
    };

    var constructor = type.GetConstructor(constructorTypes.ToArray());
    var constructorParameters = parameters.Take(constructorTypes.Count).ToList();
    var newExpr = Expression.New(constructor, constructorParameters);
    var lambdaExpr = Expression.Lambda<Func<TArg1, TArg2, TArg3, object>>(newExpr, parameters);
    var func = lambdaExpr.Compile();
    cachedFuncs.TryAdd(type, func);
    return func;
  }
}

public class TypeToIgnore
{
}
Share:
41,922
Brian Mains
Author by

Brian Mains

Computer Aid Inc. Consultant, Microsoft MVP.

Updated on July 31, 2022

Comments

  • Brian Mains
    Brian Mains almost 2 years

    I'm using RedGate to do some performance evaluation. I notice dynamically creating an instance using Activator.CreateInstance (with two constructor parameters) is taking a decent amount of time... is there a better alternative that still utilizes a reflective approach (not explicit instantiation)?

  • Drew Noakes
    Drew Noakes over 13 years
    +1. If that's still not fast enough then you might try reflection emit, but that's unlikely to yield much improvement over a compiled lambda, and it is a LOT more complicated to code.
  • 47d_
    47d_ about 12 years
    Is the Compile() operation slower or faster? I am assuming the performance is accounted without including the compile time? In scenarios where multiple instances of different types are necessary and where type of object is determined at runtime, Would it be lot slower having compile operation everytime than Activator.CreateInstance?
  • Yves M.
    Yves M. about 10 years
  • user1561358
    user1561358 over 6 years
    @47d_ I believe you are right. I just did some testing, and while it is possible I made horrible mistake, it does look like compilation eats up most resources now. Even more than Activator.
  • Jeremy
    Jeremy about 6 years
    @user1561358 the initial compile times for a lambda will be much slower than quite a few Activator.CreateInstance() calls, but you need to hold onto and reuse that same lambda throughout the process to see any useful performance boost and only if you call that lambda hundreds of times. As always, optimize where it is needed. Infrequent calls will not benefit from this. It would be nice if someone built a library that could automatically optimize based on call volume, but that may slow things down just for the profiling. Can't win. 8-)
  • Lachlan Ennis
    Lachlan Ennis almost 4 years
    Thank you! I had to change this slightly to use a string instead of type for the dictionary key. But it worked beautifully.
  • Aristos
    Aristos over 3 years
    Thank you, very nice, clear code - nice idea to use this cache.