I have been working with a system lately that requires a factory method to create a lot of open generics. To keep it simple, at the beginning, I registered each type of instance with something like the following:
var singletonFactory = new OpenGenericFactory();
StructureMap.ObjectFactory.Configure(cfg =>
{
cfg.For<IOpenGeneric<int, int>>().Use(OpenGenericFactory.GetGeneric<int, int>());
cfg.For<IOpenGeneric<int, long>>().Use(OpenGenericFactory.GetGeneric<int, long>());
cfg.For<IOpenGeneric<long, long>>().Use(OpenGenericFactory.GetGeneric<long, long>());
cfg.For<IOpenGeneric<long, int>>().Use(OpenGenericFactory.GetGeneric<long, int>());
});
The actual classes don't matter much, other than it is an open generic with two generic arguments and there would be a lot of registering. Not fun at all, especially because it is easy to forget to go register them and it's a really boring manual task. I fairly simple solution is to bind the open generic like so:
var singletonFactory = new OpenGenericFactory();
var getGenericMethod = singletonFactory.GetType().GetMethod("GetGeneric");
StructureMap.ObjectFactory.Configure(cfg =>
{
cfg.For(typeof(IOpenGeneric<,>).Use(x =>
{
var requestedType = x.BuildStack.Current.RequestedType;
var genericArgs = requestedType.GetGenericArguments();
var genericMethod = getGenericMethod.MakeGenericMethod(genericArgs);
return genericMethod.Invoke(singletonFactory, null);
});
});
This turns out to be orders of magnitude slower than registering each type by hand. One could go ahead and cache the "genericMethod" in a dictionary, but it is still pretty slow. Instead, we can create a Func out of the method and cache that. This one time compile of an expression tree (cached, of course) makes everything much faster.
using System.Linq.Expressions; // This is where the Expression tools live
var singletonFactory = new OpenGenericFactory();
var factoryType = singletonFactory.GetType();
var getGenericMethod = factoryType.GetMethod("GetGeneric");
var cache = new ConcurrentDictionary<Type, Func<OpeGenericFactory, object>>();
StructureMap.ObjectFactory.Configure(cfg =>
{
cfg.For(typeof(IOpenGeneric<,>).Use(x =>
{
var requestedType = x.BuildStack.Current.RequestedType;
var func = cache.GetOrAdd(requestedType, type =>
{
var genericArgs = type.GetGenericArguments();
var genericMethod = getGenericMethod.MakeGenericMethod(genericArgs);
var input = Expression.Parameter(factoryType, "input");
return Expression.Lambda<Func<OpenGenericFactory, object>>(Expression.Call(input, genericMethod), input).Compile();
}
return func(singletonFactory);
});
});
So, after a one-time reflection payment, we can use the method as if we were calling it directly. Fun!
No comments:
Post a Comment