aspnet/DependencyInjection — pull request #149 by halter73 (2014-12-05 00:07:36+00:00)
Title: Add (I)TypeActivator.CreateFactory

- The factory returned from `CreateFactory` should be be cached.
- Once generated, the factory is faster than `CreateInstance` since
there is no need for repeated reflection.

#105

I tested the performance of `CreateFactory` using a hacked-together microbenchmark that I ran on my workstation. Over a large number of iterations (10 million), I saw about a 4x improvement over `CreateInstance` when activating a very simple class:

// You can add this a test in TypeActivatorTests.cs
public void TypeActivatorCreateFactoryMicroBenchmark()
{
var serviceProvider = new ServiceCollection()
.AddTransient()
.AddTransient()
.BuildServiceProvider();

var typeActivator = serviceProvider.GetService();

var stopWatch = new System.Diagnostics.Stopwatch();
stopWatch.Start();

var factory = typeActivator.CreateFactory(typeof(AnotherClassAcceptingData), new[] { typeof(string), typeof(string) });
for (var i = 0; i < 10000000; i++)
{
factory(serviceProvider, new[] { "1", "2" });
}

stopWatch.Stop();
System.Console.WriteLine(stopWatch.Elapsed.TotalSeconds);
}

Sample output:
6.7144074
6.7561712
6.5328341

Results *without* compiled expressions:

// This is the TypeActivator.CreateFactory method without expression building
public Func CreateFactory(Type instanceType, Type[] parameterTypes)
{
ConstructorInfo constructor;
int?[] parameterMap;

FindApplicableConstructor(instanceType, parameterTypes, out constructor, out parameterMap);

var ctorParams = constructor.GetParameters().Select(p => p.ParameterType).ToArray();

return (sp, args) =>
{
var argsArray = new object[ctorParams.Length];

for (var i = 0; i < ctorParams.Length; i++)
{
if (parameterMap[i] != null)
{
argsArray[i] = args[parameterMap[i].Value];
}
else
{
argsArray[i] = sp.GetService(ctorParams[i]);
}
}

return constructor.Invoke(argsArray);
};
}

Sample output (same microbenchmark):
14.1813902
12.9400812
13.0804346

Results for `TypeActivator.CreateInstance`:

public void TypeActivatorCreateInstanceMicroBenchmark()
{
var serviceProvider = new ServiceCollection()
.AddTransient()
.AddTransient()
.BuildServiceProvider();

var typeActivator = serviceProvider.GetService();

var stopWatch = new System.Diagnostics.Stopwatch();
stopWatch.Start();

for (var i = 0; i < 10000000; i++)
{
typeActivator.CreateInstance(serviceProvider, typeof(AnotherClassAcceptingData), new[] { "1", "2" });
}

stopWatch.Stop();
System.Console.WriteLine(stopWatch.Elapsed.TotalSeconds);
}

Sample output:
28.1808019
31.1729289
30.7616878