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 privateTypeToIgnore
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 theFunc
.
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<string, Func<object, object, object, object>>
_instanceCreationMethods =
new Dictionary<
string,
Func<object, object, object, object>>();
/// <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
// be passed to the constructor:
var constructorParameterExpressions =
GetConstructorParameterExpressions(
lamdaParameterExpressions,
constructorArgumentTypes).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<object, object, object, object>>(
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
{
}
}
Comments
0 comments