Background:
There are times when we’d like to remove duplicated codes in instantiating types so we use generics to group similar classes and work with them easier.
Look at these classes:
public abstract class BaseType
{
protected int A { get; set; }
protected int B { get; set; }
protected BaseType(int a, int b)
{
this.A = a;
this.B = b;
}
}
public class Type1 : BaseType
{
public Type1(int a, int b) : base(a, b)
{
}
}
public class Type2 : BaseType
{
public Type2(int a, int b) : base(a, b)
{
}
}
Type1 and Type2 both inherit from the BaseType abstract class both of which require the same name and type of arguments to be instantiated.
Wouldn’t it be nice to have a generic method like
TypeCreator<T>(int a, int b) where T : BaseType
{
// instantiate type with a and b
}
Now I show you different ways to implement this.
Solutions
1) Using Activator.CreateInstance… – anti-pattern
The simplest way to implement this would be to use the Activator.CreateInstance method. There are numerous articles which says this approach has an awful performance and avoid it but I’d just like to show how it simply works:public static class MyTypeFactory<T> where T : BaseType
{
public static T Create(int a, int b)
{
return (T)Activator.CreateInstance(typeof(T), a, b);
}
}
// and use it like this
MyTypeFactory<Type1>.Create(1, 2);
2) Using a Static Delegate
For this approach to work, you’d need to provide a delegate to all your types in a Factory then use it. So it’s a bit inflexible as it has to know which types it can instantiate but it should have a very good performance:
public static class MyTypeFactory
{
static MyTypeFactory()
{
MethodRunner<Type1>.Func = (a, b) => new Type1(a, b);
MethodRunner<Type2>.Func = (a, b) => new Type2(a, b);
}
public static T Create<T>(int a, int b) where T: BaseType
{
return MethodRunner<T>.Func(a, b);
}
static class MethodRunner<T>
{
public static Func<int, int, T> Func { get; set; }
}
}
The static constructor of the MyTypeFactory class only runs once per application domain the first time the factory is accessed so it should also be thread-safe.
You can simply use it like this:
var type1 = MyTypeFactory.Create<Type1>(1, 2);
3) Using Expression Trees
In this approach, a lambda expression has to be compiled per type. So for each type a new compilation will be done initially then they're stored in a private field for further reuse. This has much better performance in comparison with Activator.CreateInstance.
Another good point about this approach is its flexibility; you don’t have to hard code the constructors like approach 2.
public static class MyTypeFactory<T> where T : BaseType
{
private static readonly Func<int, int, T> MethodDelegate = InitializeMethodDelegate();
private static Func<int, int, T> InitializeMethodDelegate()
{
var constructorInfo = typeof(T).GetConstructor(new[] { typeof(int), typeof(int) });
var param1 = Expression.Parameter(typeof(int));
var param2 = Expression.Parameter(typeof(int));
return Expression.Lambda<Func<int, int, T>>(Expression.New(constructorInfo, param1, param2), param1, param2).Compile();
}
public static T Create(int a, int b)
{
return MethodDelegate(a, b);
}
}
and you can again use it like
var type1 = MyTypeFactory<Type1>.Create(1, 2);
Be careful not to re-comiple the type each time as this may make the performance even worse than Activator.CreateInstance(). Only once per type as shown above.
The factors which should be important in your decision making are:
- Performance
- Simplicity of use
- Flexibility
No comments:
Post a Comment