Thursday, August 8, 2013

Registering Open Generics to a Factory Method with StructureMap Using Expression Trees

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