如何使用Simple Injector调度来自调解员的呼叫

时间:2019-04-06 11:52:54

标签: c# generics dependency-injection simple-injector

我有以下情况:

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>
{ ... }

在控制器中,我在构造函数中有多个CommandHandlerQueryHandler,我希望将其缩短为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类型。

有人可以帮助构建CommandHandlersQueryHandlers的中介者模式吗?

1 个答案:

答案 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中方差和泛型的工作方式(我实际上会说:在数学上),这是不可能的。

但是,有两种简单的解决方案可以解决您的问题。

  1. 使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);
    }
}
  1. 或者,如果不是使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的一部分。