Почему это не работает в SI?
Это не работает по той же причине, что и в .NET.Это будет работать только тогда, когда ваш ICommandHandler<T>
интерфейс будет определен как ковариантный , но это невозможно, потому что TCommand
является аргументом input .
Давайте удалимКонтейнер DI с картинки на мгновение.Используя обычный старый код C #, вы хотели бы выполнить следующее:
ICommandHandler<ICommand> handler1 = new Command1Handler();
ICommandHandler<ICommand> handler2 = new Command2Handler();
ICommandHandler<ICommand> handler3 = new Command3Handler();
IEnumerable<ICommandHandler<ICommand>> handlers = new[] { handler1, handler2, handler3 };
new CqrsMediator(handlers);
В предыдущем фрагменте создаются три новых обработчика команд:
Command1Handler
реализует ICommandHandler<Command1>
Command2Handler
орудия ICommandHandler<Command2>
Command3Handler
орудия ICommandHandler<Command3>
Поскольку вы хотите вставить их в CqrsMediator
, вы помещаетеих в переменных типа ICommandHandler<ICommand>
.Таким образом, вы можете легко создать массив (ICommandHandler<ICommand>[]
), который может быть вставлен в CqrsMediator
.
Этот код, однако, не компилируется. Компилятор C # определитследующее:
Ошибка CS0266: невозможно неявное преобразование типа 'Command1Handler' в 'ICommandHandler '.Существует явное преобразование (вам не хватает приведения?)
Это источник вашей проблемы.Ваши обработчики команд не могут быть преобразованы, например, с ICommandHandler<Command1>
в ICommandHandler<ICommand>
.Чтобы понять это, вам нужно узнать о ковариации и контравариантности.Возможно, вы захотите начать здесь .
Чтобы разрешить ICommandHandler<Command1>
присваиваться ICommandHandler<ICommand>
, требуется, чтобы абстракция ICommandHandler<TCommand>
была ковариантной :
public interface ICommandHandler<out TCommand> where TCommand : ICommand { ... }
Другими словами, вам нужно сделать TCommand
аргумент out
.
Но этого сделать нельзя, потому что TCommand
на самом деле является аргументом in
(таким образом, противоречивый).
Короче говоря, из-за того, как работает дисперсия и обобщение в .NET (и я бы сказал: в математике), это невозможно.
Есть два простыхРешения для вашей проблемы, однако.
- Сделайте метод
CqrsMediator.Send
универсальным:
public class CqrsMediator : ICqrsMediator
{
private readonly Container container;
public CqrsMediator(Container container) => this.container = container;
public void Send<TCommand>(TCommand command)
{
var handler = this.container.GetInstance<ICommandHandler<TCommand>>();
handler.Handle(command);
}
}
В качестве альтернативы, если сделать универсальный метод
Send
не вариант, вы также можете использовать отражение внутри метода
Send
, чтобы найти правильный тип команды:
public class CqrsMediator : ICqrsMediator
{
private readonly Container container;
public CqrsMediator(Container container) => this.container = container;
public void Send<TCommand>(TCommand command)
{
var handlerType = typeof(ICommandHandler<>).MakeGenericType(command.GetType());
dynamic handler = container.GetInstance(handlerType);
return handler.Handle((dynamic)command);
}
}
В обоихслучаи, в которых реализация CqrsMediator
зависит от Container
.Это означает, что реализация становится инфраструктурным компонентом;оно становится частью вашего корня композиции .