Having written an extension method to create an instance from a Type and been a bit underwhelmed by its performance, I looked into exactly what was happening and have now got it working much, much faster.
To recap, the problem with the first version of this method is that it cached the Funcs it created with all their argument types as object, which meant they had to be cast every time the Func was invoked. This can be seen in all the Convert calls in Visual Studio’s expression visualiser:
It turned out that this way of doing things was 10 times slower than using Activator.CreateInstance() with no constructor parameters, and took 70% of the time when one or more parameters was used. I was sure I could improve on that 🙂
With the source of the performance problem being the type of the cache forcing casting of the arguments, what I needed was a type-safe cache. The types in question are the types of the constructor parameters used when Type.GetInstance() is called, so what I needed was some sort of dynamic cache which would retain those types. I read C# in Depth recently, and picked up a handy bit of trivia to do with static, generic types.
When you use a static generic type, the C# compiler creates a singleton instance of that type for each unique combination of type parametersyou use. That means with a static, generic helper class, the compiler would create a type-safe cache for me, which I can key by the Type being constructed. So that was what I did:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
public
static class TypeExtensions {
/// <summary>
/// Returns an instance of the <paramref name="type"/> on which the method is
/// invoked.
/// </summary>
/// <param name="type">The type on which the method was invoked.</param>
/// <returns>An instance of the <paramref name="type"/>.</returns>
public
static object GetInstance(this Type type) {
return GetInstance<TypeToIgnore>(type, null);
}
/// <summary>
/// Returns an instance of the <paramref name="type"/> on which the method is
/// invoked.
/// </summary>
/// <typeparam name="TArg">The type of the argument to pass to the
/// constructor.</typeparam> <param name="type">The type on which the method
/// was invoked.</param> <param name="argument">The argument to pass to the
/// constructor.</param> <returns>An instance of the given <paramref
/// name="type"/>.</returns>
public
static object GetInstance<TArg>(this Type type, TArg argument) {
return GetInstance<TArg, TypeToIgnore>(type, argument, null);
}
/// <summary>
/// Returns an instance of the <paramref name="type"/> on which the method is
/// invoked.
/// </summary>
/// <typeparam name="TArg1">The type of the first argument to pass to the
/// constructor.</typeparam> <typeparam name="TArg2">The type of the second
/// argument to pass to the constructor.</typeparam> <param name="type">The
/// type on which the method was invoked.</param> <param name="argument1">The
/// first argument to pass to the constructor.</param> <param
/// name="argument2">The second argument to pass to the constructor.</param>
/// <returns>An instance of the given <paramref name="type"/>.</returns>
public
static object GetInstance<TArg1, TArg2>(this Type type, TArg1 argument1,
TArg2 argument2) {
return GetInstance<TArg1, TArg2, TypeToIgnore>(type, argument1, argument2,
null);
}
/// <summary>
/// Returns an instance of the <paramref name="type"/> on which the method is
/// invoked.
/// </summary>
/// <typeparam name="TArg1">The type of the first argument to pass to the
/// constructor.</typeparam> <typeparam name="TArg2">The type of the second
/// argument to pass to the constructor.</typeparam> <typeparam
/// name="TArg3">The type of the third argument to pass to the
/// constructor.</typeparam> <param name="type">The type on which the method
/// was invoked.</param> <param name="argument1">The first argument to pass to
/// the constructor.</param> <param name="argument2">The second argument to
/// pass to the constructor.</param> <param name="argument3">The third
/// argument to pass to the constructor.</param> <returns>An instance of the
/// given <paramref name="type"/>.</returns>
public
static object GetInstance<TArg1, TArg2, TArg3>(this Type type,
TArg1 argument1,
TArg2 argument2,
TArg3 argument3) {
return InstanceCreationFactory<TArg1, TArg2, TArg3>.CreateInstanceOf(
type, argument1, argument2, argument3);
}
// To allow for overloads with differing numbers of arguments, we flag
// arguments which should be ignored by using this Type:
private
class TypeToIgnore {
}
private static class InstanceCreationFactory<TArg1, TArg2, TArg3> {
// This dictionary will hold a cache of object-creation functions, keyed by
// the Type to create:
private
static readonly Dictionary<Type, Func<TArg1, TArg2, TArg3, object>>
_instanceCreationMethods =
new Dictionary<Type, Func<TArg1, TArg2, TArg3, object>>();
public
static object CreateInstanceOf(Type type, TArg1 arg1, TArg2 arg2,
TArg3 arg3) {
CacheInstanceCreationMethodIfRequired(type);
return _instanceCreationMethods[type].Invoke(arg1, arg2, arg3);
}
private
static void CacheInstanceCreationMethodIfRequired(Type type) {
// Bail out if we've already cached the instance creation method:
if (_instanceCreationMethods.ContainsKey(type)) {
return;
}
var argumentTypes = new[]{typeof(TArg1), typeof(TArg2), typeof(TArg3)};
// Get a collection of the constructor argument Types we've been given;
// ignore any arguments which are of the 'ignore this' Type:
Type[] constructorArgumentTypes =
argumentTypes.Where(t = > t != typeof(TypeToIgnore)).ToArray();
// Get the Constructor which matches the given argument Types:
var constructor = type.GetConstructor(
BindingFlags.Instance | BindingFlags.Public, null,
CallingConventions.HasThis, constructorArgumentTypes,
new ParameterModifier[0]);
// Get a set of Expressions representing the parameters which will be
// passed to the Func:
var lamdaParameterExpressions =
new[]{Expression.Parameter(typeof(TArg1), "param1"),
Expression.Parameter(typeof(TArg2), "param2"),
Expression.Parameter(typeof(TArg3), "param3")};
// Get a set of Expressions representing the parameters which will be
// passed to the constructor:
var constructorParameterExpressions =
lamdaParameterExpressions.Take(constructorArgumentTypes.Length)
.ToArray();
// Get an Expression representing the constructor call, passing in the
// constructor parameters:
var constructorCallExpression =
Expression.New(constructor, constructorParameterExpressions);
// Compile the Expression into a Func which takes three arguments and
// returns the constructed object:
var constructorCallingLambda =
Expression
.Lambda<Func<TArg1, TArg2, TArg3, object>>(
constructorCallExpression, lamdaParameterExpressions)
.Compile();
_instanceCreationMethods[type] = constructorCallingLambda;
}
}
}
The Funcs created by this version of the method now look like this in the Expression Visualiser:
That’s more like it – no casting! So how does it perform compared to the previous set of methods? Well, here’s the output of the same performance measurement I ran before; the third column shows the total number of ticks taken to create 100,000 objects, the fourth the number of ticks taken per object:
To sum up the improvement:
- An instance is now created in 5 ticks – between 10% and 7% of the time taken by Activator.CreateInstance()
- Because the arguments aren’t cast, performance doesn’t degrade when constuctors are used with extra parameters
That’s more like it! I was especially pleased to be able to apply a bit of trivia I read from a C# book to a problem like this and see a genuine improvement. The more I use and get used to Expression Trees, the more I like them and find uses for them 🙂