我有以下情况:
public interface ICommand { }
public interface ICommandHandler<TCommand> where TCommand : ICommand
{
void Handle(TCommand command);
}
public interface ISepsCommandHandler<TCommand> : ICommandHandler<TCommand>
where TCommand : ICommand
{
event EventHandler<EntityExecutionLoggingEventArgs> UseCaseExecutionProcessing;
}
public sealed class CalculateNewAverageElectricEnergyProductionPriceCommandHandler
: BaseCommandHandler,
ISepsCommandHandler<CalculateNewAverageElectricEnergyProductionPriceCommand>
{ ... }
public sealed class CalculateCpiCommandHandler
: BaseCommandHandler, ISepsCommandHandler<CalculateNewConsumerPriceIndexCommand>
{ ... }
在控制器中,我在构造函数中有多个CommandHandler
和QueryHandler
,我希望将其缩短为MediatR之类的中介模式。
public interface ICqrsMediator // <TCommand, TQuery, TQueryResult>
// where TCommand : ICommand
{
void Send(ICommand command);
}
public class CqrsMediator : ICqrsMediator // <ICommand
// where TCommand : ICommand
{
private readonly IDictionary<Type, ICommandHandler<ICommand>> _commands;
public CqrsMediator(
IEnumerable<ICommandHandler<ICommand>> commands) { ... }
...
}
问题:
我想将ICommandHandler
的集合解析为CqrsMediator
的构造函数。我尝试了多种方法。
问题: 为什么这在SI中不起作用?
var bla = GetAllInstances(typeof(ICommandHandler<ICommand>));
我收到一条消息,它找不到ICommandHandler<ICommand>
,但是ICommandHandler<TCommand>
已注册,尽管我在泛型中给出了一个限制,即TCommand只能是ICommand类型。
有人可以帮助构建CommandHandlers和QueryHandlers的中介者模式吗?
答案 0 :(得分:1)
为什么这在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>
。要了解这一点,您需要了解协方差和逆方差。您可能要启动here。
要允许将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
。这意味着实施成为基础设施的组成部分。它成为您Composition Root的一部分。