First, sorry for the vague question title. I couldn’t come up with a more precise one.
Given these types:
{ TCommand : ICommand }
«interface» «interface» /
+-----------+ +----------------------/----+
| ICommand | | ICommandHandler<TCommand> |
+-----------+ +---------------------------+
^ | Handle(command: TCommand) |
| +---------------------------+
| ^
| |
+------------+ +-------------------+
| FooCommand | | FooCommandHandler |
+------------+ +-------------------+
^
|
+-------------------+
| SpecialFooCommand |
+-------------------+
I would like to write a method Dispatch that accepts any command and sends it to an appropriate ICommandHandler<>. I thought that using a DI container (Autofac) might greatly simplify the mapping from a command’s type to a command handler:
void Dispatch<TCommand>(TCommand command) where TCommand : ICommand
{
var handler = autofacContainer.Resolve<ICommandHandler<TCommand>>();
handler.Handle(command);
}
Let’s say the DI container knows about all the types shown above. Now I’m calling:
Dispatch(new SpecialFooCommand(…));
In reality, this will result in Autofac throwing a ComponentNotRegisteredException, since there is no ICommandHandler<SpecialFooCommand> available.
Ideally however, I would still want a SpecialFooCommand to be handled by the closest-matching command handler available, ie. by a FooCommandHandler in the above example.
Can Autofac be customized towards that end, perhaps with a custom registration source?
P.S.: I understand that there might be the fundamental problem of co-/contravariance getting in the way (as in the following example), and that the only solution might be one that doesn’t use generics at all… but I would want to stick to generic types, if possible.
ICommandHandler<FooCommand> fooHandler = new FooCommandHandler(…);
ICommandHandler<ICommand> handler = fooHandler;
// ^
// doesn't work, types are incompatible
Not really a fair answer, as I’ve extended Autofac since you posted the question… 🙂
As per Daniel’s answer, you’ll need to add the
inmodifier to theTCommandparameter ofICommandHandler:Autofac 2.5.2 now includes an
IRegistrationSourceto enable contravariantResolve()operations:With this source registered, services represented by a generic interface with a single
inparameter will be looked up taking variant implementations into account:Both calls to
Resolve()will successfully retrieve theFooCommandHandler.If you can’t upgrade to the latest Autofac package, grab the
ContravariantRegistrationSourcefrom http://code.google.com/p/autofac/source/browse/src/Source/Autofac/Features/Variance/ContravariantRegistrationSource.cs – it should compile against any recent Autofac build.