Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Customizing Autofac's component resolution / Issue with generic co-/contravariance

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
like image 745
stakx - no longer contributing Avatar asked Aug 10 '11 11:08

stakx - no longer contributing


1 Answers

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 in modifier to the TCommand parameter of ICommandHandler:

interface ICommandHandler<in TCommand>
{
    void Handle(TCommand command);
}

Autofac 2.5.2 now includes an IRegistrationSource to enable contravariant Resolve() operations:

using Autofac.Features.Variance;

var builder = new ContainerBuilder();
builder.RegisterSource(new ContravariantRegistrationSource());

With this source registered, services represented by a generic interface with a single in parameter will be looked up taking variant implementations into account:

builder.RegisterType<FooCommandHandler>()
   .As<ICommandHandler<FooCommand>>();

var container = builder.Build();
container.Resolve<ICommandHandler<FooCommand>>();
container.Resolve<ICommandHandler<SpecialFooCommand>>();

Both calls to Resolve() will successfully retrieve the FooCommandHandler.

If you can't upgrade to the latest Autofac package, grab the ContravariantRegistrationSource from http://code.google.com/p/autofac/source/browse/src/Source/Autofac/Features/Variance/ContravariantRegistrationSource.cs - it should compile against any recent Autofac build.

like image 106
Nicholas Blumhardt Avatar answered Sep 28 '22 22:09

Nicholas Blumhardt