Injecting Generic type parameters with AutoFac

2019-04-12 01:27发布

I think I am really confused about what I can do with AutoFac, can someone please put me on track.

I have a base type

class PersonBase{ 
    public string SaySomething(){
       return "I am base";
    }
}

I derive two concrete classes

class FakePerson : PersonBase{
    public override string SaySomething(){
       return "I'm so Fake";
    }
}

class RealPerson : PersonBase{
    public override string SaySomething(){
       return "I am For Real";
    }
}

Create a generic class, PersonHandler, to deal with different types of people and would like the PersonHandler to instantiate the person at the appropriate time, so I do not want an instance of Person injected, just need to derived type

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

    T _Person;    

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

Now I try to use the handler, after registering the types as detailed next, with varying results.

var ph = contrainer.Resolve<PersonHandler<PersonBase>>();
ph.DoWork();

I attempted to register the types as follows

1. vBuilder.RegisterType<PersonHandler<FakePerson>>().As<PersonHandler<PersonBase>>();

This gives me an error stating the PersonHandler<FakePerson> is not assignable to PersonHandler<PersonBase> (or the other way around I don't recal which)

2. vBuilder.RegisterGeneric<typeof(PersonHandler<>)>
   vBuilder.RegisterType<FakePerson>().As<PersonBase>();

This does not resolve PersonBase to FakePerson, but just gives PersonHandler<PersonBase>, So it results in "I am Base"

3. vBuilder.RegisterGeneric(typeof(PersonHandler<FakePerson>)).As(typeof(PersonHandler<PersonBase>));

This given an error saying that PersonHandler<FakePerson> is not an open type

So now I have been chasing my tale all day and, frankly, it is getting quit tedious,

PLEASE HELP

2条回答
三岁会撩人
2楼-- · 2019-04-12 01:42

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>.

查看更多
劳资没心,怎么记你
3楼-- · 2019-04-12 01:52

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.

查看更多
登录 后发表回答