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
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.
来源:https://stackoverflow.com/questions/4726167/injecting-generic-type-parameters-with-autofac