Подтвердить что ты не робот

Настройка разрешения компонента Autofac/проблема с общей ко-контравариантностью

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

Учитывая эти типы:

                                                     { 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
4b9b3361

Ответ 1

Не совсем честный ответ, поскольку я расширил 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 build.

Ответ 2

То, что вы просите, невозможно без собственного кодирования. В основном, вы спрашиваете следующее: Если тип, который я пытался разрешить, не найден, верните другой тип, который можно преобразовать в него, например. если вы попытаетесь разрешить 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);

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

Ответ 3

Мне нравится добавлять альтернативный подход, который также работает без поддержки дисперсии С# 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.