Настройка разрешения / проблемы компонента Autofac с помощью общей ко-/ контравариантности - PullRequest
18 голосов
/ 10 августа 2011

Во-первых, извините за смутное название вопроса. Я не мог придумать более точный.

С учетом этих типов:

                                                     { TCommand : ICommand }
       «interface»                   «interface»    /
      +-----------+         +----------------------/----+
      | ICommand  |         | ICommandHandler<TCommand> |
      +-----------+         +---------------------------+
            ^               | Handle(command: TCommand) |
            |               +---------------------------+
            |                              ^
            |                              |
      +------------+            +-------------------+
      | FooCommand |            | FooCommandHandler |
      +------------+            +-------------------+
            ^
            |
   +-------------------+
   | SpecialFooCommand |
   +-------------------+

Я хотел бы написать метод Dispatch, который принимает любую команду и отправляет ее соответствующему ICommandHandler<>. Я подумал, что использование контейнера DI (Autofac) может значительно упростить отображение типа команды в обработчик команды:

void Dispatch<TCommand>(TCommand command) where TCommand : ICommand
{
    var handler = autofacContainer.Resolve<ICommandHandler<TCommand>>();
    handler.Handle(command);
}

Допустим, контейнер DI знает обо всех типах, показанных выше. Сейчас я звоню:

Dispatch(new SpecialFooCommand(…));

В действительности это приведет к тому, что Autofac выбросит ComponentNotRegisteredException, поскольку ICommandHandler<SpecialFooCommand> недоступно.

В идеале, однако, я бы все же хотел, чтобы SpecialFooCommand обрабатывался доступным обработчиком команд, наиболее близким к соответствующему, т.е. на FooCommandHandler в приведенном выше примере.

Можно ли настроить Autofac для этой цели, возможно, с помощью специального источника регистрации?


PS: Я понимаю, что может быть фундаментальная проблема со-/ контравариантности, мешающая (как в следующем примере), и что единственным решением может быть решение, которое не использует дженерики вообще ... но я бы хотел придерживаться дженериков, если это возможно.

ICommandHandler<FooCommand> fooHandler = new FooCommandHandler(…);
ICommandHandler<ICommand> handler = fooHandler;
//                                ^
//              doesn't work, types are incompatible

Ответы [ 3 ]

17 голосов
/ 12 августа 2011

Не совсем правильный ответ, так как я расширил Autofac с тех пор, как вы опубликовали вопрос ...:)

Согласно ответу Даниила, вам нужно добавить модификатор in к параметру TCommand ICommandHandler:

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

Autofac 2.5.2 теперь включает IRegistrationSource для включения контравариантных Resolve() операций:

using Autofac.Features.Variance;

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

После регистрации этого источника сервисы, представленные универсальным интерфейсом с одним параметром in, будут найдены с учетом вариантов реализации:

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

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

Оба вызова на Resolve() будут успешно получать FooCommandHandler.

Если вы не можете выполнить обновление до последней версии пакета Autofac, возьмите ContravariantRegistrationSource с http://code.google.com/p/autofac/source/browse/src/Source/Autofac/Features/Variance/ContravariantRegistrationSource.cs - он должен скомпилироваться с любой последней сборкой Autofac.

5 голосов
/ 10 августа 2011

То, что вы спрашиваете, невозможно без собственного кодирования. По сути, вы спрашиваете следующее: Если тип, который я пытался разрешить, не найден, верните другой тип, который можно преобразовать в него, например, если вы попытаетесь разрешить IEnumerable, верните тип, зарегистрированный для ICollection. Это не поддерживается Одним из простых решений будет следующее: Зарегистрируйте FooCommandHandler в качестве обработчика для ICommandHandler<SpecialFooCommand>. Чтобы это работало, ICommandHandler должен быть контравариантным :

interface ICommand { }

class FooCommand : ICommand { }

class SpecialFooCommand : FooCommand { }

interface ICommandHandler<in T> where T : ICommand
{
    void Handle(T command);
}

class FooCommandHandler : ICommandHandler<FooCommand>
{
    public void Handle(FooCommand command)
    {
        // ...
    }
}

var builder = new ContainerBuilder();
builder.RegisterType<FooCommandHandler>()
       .As<ICommandHandler<SpecialFooCommand>>()
       .As<ICommandHandler<FooCommand>>();
var container = builder.Build();
var fooCommand = new FooCommand();
var specialCommand = new SpecialFooCommand();
container.Resolve<ICommandHandler<FooCommand>>().Handle(fooCommand);
container.Resolve<ICommandHandler<FooCommand>>().Handle(specialCommand);
container.Resolve<ICommandHandler<SpecialFooCommand>>().Handle(specialCommand);

Кстати: при использовании контейнера вы применяете Anti-pattern локатора службы . Этого следует избегать.

3 голосов
/ 14 августа 2011

Мне нравится добавлять альтернативный подход, который также работает без поддержки дисперсии C # 4.0.

Вы можете создать специальный декоратор / оболочку, которая позволяет выполнять команду в качестве базового типа:

public class VarianceHandler<TSubCommand, TBaseCommand> 
    : ICommandHandler<TSubCommand>
    where TSubCommand : TBaseCommand
{
    private readonly ICommandHandler<TBaseCommand> handler;

    public VarianceHandler(ICommandHandler<TBaseCommand> handler)
    {
        this.handler = handler;
    }

    public void Handle(TSubCommand command)
    {
        this.handler.Handle(command);
    }
}

С учетом этого следующая строка кода позволит вам обрабатывать SpecialFooCommand в качестве базового типа:

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

builder.Register<VarianceHandler<SpecialFooCommand, FooCommand>>()
    .As<ICommandHandler<SpecialFooCommand>>();

Обратите внимание, что использование таких VarianceHandler работает для большинства контейнеров DI.

...