I've been doing a bit of testing with this and as a follow-up to Miron's original article (here), I've discovered that the .NET 4.0 Activator is much faster than before. Some results from a version of his app tweaked to show timings in milliseconds:
.NET 3.5 build
Number of iterates: 1000000
Activator.CreateInstance(Type): 4150
Activator.CreateInstance<T>(): 1288
FastObjectFactory.CreateObjec (empty cache): 33
FastObjectFactory.CreateObjec (cache full): 28
ItemFactory.GetNewItem: 1283
.NET 4.0 build
Number of iterates: 1000000
Activator.CreateInstance(Type): 138
Activator.CreateInstance<T>(): 151
FastObjectFactory.CreateObjec (empty cache): 28
FastObjectFactory.CreateObjec (cache full): 22
ItemFactory.GetNewItem: 156
However this was for a no-parameter constructor and I also noticed that the activator is still a little slow when constructors with parameters are used, as can bse seen below.
One problem I had with the original solution posted here is that I don't necessarily know the type of the objects I want at compile time - I only have a Type reference. Now (unless I'm being a duffer) that means I can't use the generic solution here or a simple variation on it.
So this is a version I've knocked together which addresses the problem. It also showed up the slight slowness in the .NET 4.0 Activator when constructor parameters are used:
// For use with no-parameter constructors. Also contains constants and utility methods
public static class FastActivator
{
// THIS VERSION NOT THREAD SAFE YET
static Dictionary<Type, Func<object>> constructorCache = new Dictionary<Type, Func<object>>();
private const string DynamicMethodPrefix = "DM$_FastActivator_";
public static object CreateInstance(Type objType)
{
return GetConstructor(objType)();
}
public static Func<object> GetConstructor(Type objType)
{
Func<object> constructor;
if (!constructorCache.TryGetValue(objType, out constructor))
{
constructor = (Func<object>)FastActivator.BuildConstructorDelegate(objType, typeof(Func<object>), new Type[] { });
constructorCache.Add(objType, constructor);
}
return constructor;
}
public static object BuildConstructorDelegate(Type objType, Type delegateType, Type[] argTypes)
{
var dynMethod = new DynamicMethod(DynamicMethodPrefix + objType.Name + "$" + argTypes.Length.ToString(), objType, argTypes, objType);
ILGenerator ilGen = dynMethod.GetILGenerator();
for (int argIdx = 0; argIdx < argTypes.Length; argIdx++)
{
ilGen.Emit(OpCodes.Ldarg, argIdx);
}
ilGen.Emit(OpCodes.Newobj, objType.GetConstructor(argTypes));
ilGen.Emit(OpCodes.Ret);
return dynMethod.CreateDelegate(delegateType);
}
}
// For use with one-parameter constructors, argument type = T1
public static class FastActivator<T1>
{
// THIS VERSION NOT THREAD SAFE YET
static Dictionary<Type, Func<T1, object>> constructorCache = new Dictionary<Type, Func<T1, object>>();
public static object CreateInstance(Type objType, T1 arg1)
{
return GetConstructor(objType, new Type[] { typeof(T1) })(arg1);
}
public static Func<T1, object> GetConstructor(Type objType, Type[] argTypes)
{
Func<T1, object> constructor;
if (!constructorCache.TryGetValue(objType, out constructor))
{
constructor = (Func<T1, object>)FastActivator.BuildConstructorDelegate(objType, typeof(Func<T1, object>), argTypes);
constructorCache.Add(objType, constructor);
}
return constructor;
}
}
// For use with two-parameter constructors, argument types = T1, T2
public static class FastActivator<T1, T2>
{
// THIS VERSION NOT THREAD SAFE YET
static Dictionary<Type, Func<T1, T2, object>> constructorCache = new Dictionary<Type, Func<T1, T2, object>>();
public static object CreateInstance(Type objType, T1 arg1, T2 arg2)
{
return GetConstructor(objType, new Type[] { typeof(T1), typeof(T2) })(arg1, arg2);
}
public static Func<T1, T2, object> GetConstructor(Type objType, Type[] argTypes)
{
Func<T1, T2, object> constructor;
if (!constructorCache.TryGetValue(objType, out constructor))
{
constructor = (Func<T1, T2, object>)FastActivator.BuildConstructorDelegate(objType, typeof(Func<T1, T2, object>), argTypes);
constructorCache.Add(objType, constructor);
}
return constructor;
}
}
// For use with three-parameter constructors, argument types = T1, T2, T3
// NB: could possibly merge these FastActivator<T1,...> classes and avoid generic type parameters
// but would need to take care that cache entries were keyed to distinguish constructors having
// the same number of parameters but of different types. Keep separate for now.
public static class FastActivator<T1, T2, T3>
{
// THIS VERSION NOT THREAD SAFE YET
static Dictionary<Type, Func<T1, T2, T3, object>> constructorCache = new Dictionary<Type, Func<T1, T2, T3, object>>();
public static object CreateInstance(Type objType, T1 arg1, T2 arg2, T3 arg3)
{
return GetConstructor(objType, new Type[] { typeof(T1), typeof(T2), typeof(T3) })(arg1, arg2, arg3);
}
public static Func<T1, T2, T3, object> GetConstructor(Type objType, Type[] argTypes)
{
Func<T1, T2, T3, object> constructor;
if (!constructorCache.TryGetValue(objType, out constructor))
{
constructor = (Func<T1, T2, T3, object>)FastActivator.BuildConstructorDelegate(objType, typeof(Func<T1, T2, T3, object>), argTypes);
constructorCache.Add(objType, constructor);
}
return constructor;
}
}
Some performance results below. Note this is for creation of 1 million objects and timings in milliseconds again:
Activator.CreateInstance(objType) - parameterless constructor: 153
FastActivator.CreateInstance(objType) - parameterless constructor: 86
Using FastActivator.GetConstructor and calling it repeatedly - parameterless constructor: 34
Activator.CreateInstance(objType) with 1 constructor arg: 3183
FastActivator.CreateInstance(objType) with 1 constructor arg: 257
FastActivator.GetConstructor and calling it repeatedly with 1 constructor arg: 126
Activator.CreateInstance(objType) with 3 constructor args: 4403
FastActivator.CreateInstance(objType) with 3 constructor args: 640
FastActivator.GetConstructor and calling it repeatedly with 3 constructor args : 405
FastActivator.GetConstructor and calling it repeatedly with 3 constructor args; args created only once : 19