A C# Extension Method using Expression Trees to Create an instance from a Type

Edit: I've now written an improved version of this set of extension methods, which can be found here. Consider these deprecated!

I recently wrote a Type.GetInstance() extension method, and used the opportunity to play around with Expression Trees, which I'd recently read up on in C# in Depth. Here's the set of extension methods I came up with, which allow you to quickly create an instance of a Type from the Type itself; like this:

// No constructor arguments:
MyClass myClassInstance = (MyClass)typeof(MyClass).GetInstance();

// One constructor argument:
MyClass myClassInstance = (MyClass)typeof(MyClass).GetInstance(argument1);

// Three constructor arguments:
MyClass myClassInstance = (MyClass)typeof(MyClass).GetInstance(argument1, argument2, argument3);

Where would you use this? Well, for example to create an instance of an object from a generic type argument; I'm now using it in my generic WCF client. I tend to find myself looking up Types at runtime quite often, and this gives me a neat and fast way of creating instances.

Things to note:

  • An Expression Tree is a tree of objects which can be compiled into an executable method.

  • The Expression Tree below creates a Func which accepts up to three constructor arguments and returns the constructed object. I wanted overloads which took fewer arguments and for them to call the one which took the most, so I added a private TypeToIgnore class which is used solely to identify a constructor argument which should be ignored when putting together the set of constructor parameters.

  • To make extra extension methods which take more arguments, you'd need to add an extra object parameter to the Func.

Here's the C# 4 code; I've commented it quite heavily, so hopefully it'll make sense:

using System;
using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; using System.Reflection; public static class TypeExtensions {
// This dictionary will hold a cache of object-creation functions, keyed by the constructor signature      private static readonly Dictionary<stringFunc<objectobjectobjectobject>> _instanceCreationMethods =         new Dictionary<stringFunc<objectobjectobjectobject>>();     /// <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)     {         string constructorSignatureKey;         var argumentTypes = new[] { typeof(TArg1), typeof(TArg2), typeof(TArg3) };         CacheInstanceCreationMethodIfRequired(type, argumentTypes, out constructorSignatureKey);         return _instanceCreationMethods[constructorSignatureKey].Invoke(argument1, argument2, argument3);     }     private static void CacheInstanceCreationMethodIfRequired(         Type type,         Type[] argumentTypes,         out string constructorSignatureKey)     {
// Make a constructor signature key unique to the Type and argument we've been given; ignore
// any arguments which are of the 'ignore this' Type:
        Type[] constructorArgumentTypes = argumentTypes.Where(t => t != typeof(TypeToIgnore)).ToArray();         constructorSignatureKey = GetConstructorSignatureKey(type, constructorArgumentTypes);
// Bail out if we've already cached the instance creation method:         if (_instanceCreationMethods.ContainsKey(constructorSignatureKey))         {             return;         }
// 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 = GetLambdaParameterExpressions(argumentTypes).ToArray();
// Get a set of Expressions representing the parameters which will be passed to the constructor:         var constructorParameterExpressions = GetConstructorParameterExpressions(
// 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<objectobjectobjectobject>>(constructorCallExpression, lamdaParameterExpressions)             .Compile();         _instanceCreationMethods.Add(constructorSignatureKey, constructorCallingLambda);     }     private static IEnumerable<ParameterExpression> GetLambdaParameterExpressions(Type[] argumentTypes)     {         for (int i = 0; i < argumentTypes.Length; i++)         {             yield return Expression.Parameter(typeof(object), string.Concat("param", i));         }     }     private static IEnumerable<UnaryExpression> GetConstructorParameterExpressions(         ParameterExpression[] lamdaParameterExpressions,         Type[] constructorArgumentTypes)     {         for (int i = 0; i < constructorArgumentTypes.Length; i++)         {
// Each parameter passed to the lambda is of type object, so we need to convert it into
// the appropriate type for the constructor:
            yield return Expression.Convert(lamdaParameterExpressions[i], constructorArgumentTypes[i]);         }     }     private static string GetConstructorSignatureKey(Type type, Type[] argumentTypes)     {         return string.Concat(type.FullName, " ("string.Join(", ", argumentTypes.Select(at => at.FullName)), ")");     }
// To allow for overloads with differing numbers of arguments, we flag arguments which should be
// ignored by using this Type:
    private class TypeToIgnore     {     }

Print | posted @ Wednesday, January 11, 2012 8:46 PM

Comments on this entry:

No comments posted yet.

Post A Comment