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