Injecting Generic type parameters with AutoFac

戏子无情 提交于 2019-12-04 12:34:16
Nicholas Blumhardt

The (almost) correct solution is this one that you already tried:

builder.RegisterType<PersonHandler<FakePerson>>()
    .As<PersonHandler<PersonBase>>();

The reason Autofac gave you an error is that generic class types in C# don't work this way.

That is, you can't write:

PersonHandler<PersonBase> ph = new PersonHandler<FakePerson>();

(Give it a try in your IDE - the compiler will reject it.)

The reason for this is that contravariance, the required feature added in C#4, only supports interface types.

To cut a long story short, if you create IPersonHandler<T> and use that as the service then the above code will work as expected:

interface IPersonHandler<in T>
    where T : PersonBase, new() {

    void DoWork();
}

Note the in parameter on the interface declaration.

Make PersonHandler<T> implement IPersonHandler<T>:

class PersonHandler<T> : IPersonHandler<T>
    where T : PersonBase, new() {

Then register like so:

builder.RegisterType<PersonHandler<FakePerson>>()
    .As<IPersonHandler<PersonBase>>();

You can then get handlers using:

IPersonHandler<PersonBase> handler =
    container.Resolve<IPersonHandler<PersonBase>>();

The returned handler object will be of type PersonHandler<FakePerson>.

When you resolve a service that is registered with an open generic type (such as PersonHandler<>) you need to specify the exact closed type that you want to handle, and Autofac will generate one for you.

In this case, you called container.Resolve<PersonHandler<PersonBase>>() so you were returned a PersonHandler<PersonBase>. Now, if you look at your PersonHandler class, and substitute PersonBase for T, the resulting DoWork method looks like this:

public DoWork(){
    _Person = new PersonBase();
    _Person.SaySomething();
}

So the SaySomething() call uses the method on the base class.

I'm not sure exactly what you are trying to do, but it looks like you want to decide which concrete PersonBase implementation to use during registration. If this is the case, you might try using the Delegate Factory feature of Autofac instead:

class PersonHandler
{
    private readonly Func<PersonBase> createPerson;

    public PersonHandler(Func<PersonBase> createPerson)
    {
        this.createPerson = createPerson;
    }

    public void DoWork()
    {
        PersonBase pb = this.createPerson();
        Console.WriteLine(pb.SaySomething());
    }
}

Then you can register your classes as follows:

ContainerBuilder builder = new ContainerBuilder();
builder.RegisterType<RealPerson>().As<PersonBase>();
builder.RegisterType<PersonHandler>();
var context = builder.Build();
var handler = context.Resolve<PersonHandler>();
handler.DoWork();

By varying the second line of this code, you can register whichever type of PersonBase you actually want to use in the program.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!