Registering Method on Generic Factory with Structu

2019-02-24 03:35发布

问题:

I am trying to use a method on a generic factory class in my structuremap registry. Normally, i would use the following when registering a type using a factory method:

For<Foo>().Use(x => new FooFactory().GetFoo());

And the following when registering a generic type:

For(typeof(ISomeGeneric<>)).Use(typeof(SomeGeneric<>));

How can I combine the two and retrieve a generic type from a generic factory method? I think it should be something like:

For(typeof(IFoo<>)).Use(typeof(x => new FooFactory<>().Create(someParameter));

This just gives a

"Cannot convert lambda expression to type object because it is not a delegate type" 

error. I've tried various combinations but am stumped. Any ideas?

Thanks.

回答1:

This is possible, BUT I would look for an alternative if you can. The issue is that to work with the open generic, you have to use some reflection. This means you will take a performance hit.

public class FooRegistry:Registry
{
    public FooRegistry()
    {
        For(typeof(IFoo<>)).Use(x =>
        {
            var ParamType = x.BuildStack.Current.RequestedType
                             .GetGenericArguments()[0];
            return BuildUsingFooFactory(ParamType);
        });
    }

    private object BuildUsingFooFactory(Type paramType)
    {
        var factoryType = typeof (FooFactory<>).MakeGenericType(new[] {paramType});
        var createMethod = factoryType.GetMethod("Create");
        object factory = Activator.CreateInstance(factoryType);
        return createMethod.Invoke(factory, new[] {"SomeParameterString"});
    }
}

public class FooFactory<T>
{
    public IFoo<T> Create(string param)
    {
        return new Foo<T>();
    }

}

public interface IFoo<T>
{
}

public class Foo<T> : IFoo<T>
{   
}

What you are doing in order is the following:

  1. Find out what the requested generic argument is. (ParamType)
  2. Create a non-open generic type for the factory (factoryType)
  3. Grab the create method off of it. (createMethod)
  4. Create an instance of the factory using the Activator (factory)
  5. Call the create method on the factory instance with your some parameter.

StructureMap takes care of casting the output to the right interface.

Better Solution

Instead of using the IFoo directly, use the IFooFactory. This makes it much cleaner, you have an open generic mapping to the IFooFactory<>. Then just get the type of FooFactory you need to generate your objects.

public class FooRegistry:Registry
{
    public FooRegistry()
    {
        For(typeof (IFooFactory<>))
            .Use(typeof (FooFactory<>))
            .CtorDependency<string>("connection")
            .Is("SomeConnectionString");
    }
}


public interface IFooFactory<T>
{
    IFoo<T> CreateInstance();
}

public class FooFactory<T> : IFooFactory<T>
{
    public FooFactory(string connection)
    {
    } 

    public IFoo<T> CreateInstance()
    {
        return new Foo<T>();
    }
}

public interface IFoo<T>
{
}

public class Foo<T> : IFoo<T>
{   
}