界面协方差逆变:为什么这不编译?

Chr*_*hac 4 c# generics covariance contravariance c#-4.0

我想实现一些CommandBus可以做到的.DispatchCommandsCommandHandlers

  • A Command是一个简单的DTO,描述了应该发生的事情.例如:"增量计数器减5"
  • A CommandHandler能够处理精确类型Command.
  • CommandBus采取Command和执行CommandHandler即能处理它.

我写的代码不编译.

编译器抱怨cannot convert from 'IncrementHandler' to 'Handler<Command>'.我不明白为什么,因为IncrementHandler实现Handler<Increment>Increment实现Command

我已经尝试了通用接口上的两个inout修饰符,它没有解决问题.

有没有办法只用接口来实现这个目标?

[TestClass]
public class CommandBusTest
{
  [TestMethod]
  public void DispatchesProperly()
  {
    var handler = new IncrementHandler(counter: 0);
    var bus = new CommandBus(handler); // <--Doesn't compile: cannot convert from 'IncrementHandler' to 'Handler<Command>'
    bus.Dispatch(new Increment(5));
    Assert.AreEqual(5, handler.Counter);
  }
}

public class CommandBus
{
  private readonly Dictionary<Type, Handler<Command>> handlers;

  public CommandBus(params Handler<Command>[] handlers)
  {
    this.handlers = handlers.ToDictionary(
      h => h.HandledCommand,
      h => h);
  }

  public void Dispatch(Command commande) { /*...*/ }
}

public interface Command { }

public interface Handler<TCommand> where TCommand : Command
{
  Type HandledCommand { get; }
  void Handle(TCommand command);
}

public class Increment : Command
{
  public Increment(int value) { Value = value; }

  public int Value { get; }
}

public class IncrementHandler : Handler<Increment>
{
  // Handler<Increment>
  public Type HandledCommand => typeof(Increment);
  public void Handle(Increment command)
  {
    Counter += command.Value;
  }
  // Handler<Increment>

  public int Counter { get; private set; }

  public IncrementHandler(int counter)
  {
    Counter = counter;
  }
}
Run Code Online (Sandbox Code Playgroud)

Eri*_*ert 8

我不明白为什么,因为IncrementHandler实现Handler<Increment>Increment实现Command

让我们解决你的误解,其余的将变得清晰.

假设你想做的事情是合法的.出了什么问题?

IncrementHandler ih = whatever;
Handler<Command> h = ih; // This is illegal. Suppose it is legal.
Run Code Online (Sandbox Code Playgroud)

现在我们上课了

public class Decrement : Command { ... }
Run Code Online (Sandbox Code Playgroud)

现在我们将它传递给h:

Decrement d = new Decrement();
h.Handle(d);
Run Code Online (Sandbox Code Playgroud)

这是合法的,因为Handler<Command>.Handle需要a Command,而a Decrement是a Command.

所以发生了什么事?你刚刚通过递减命令ih,通过h,但是ih是一个IncrementHandler只知道如何处理的增量.

由于这是荒谬的,这里的某些东西必须是非法的; 您希望哪条线路非法?C#团队认为转换是非法的.

进一步来说:

您的程序在类型系统的安全检查中尝试使用反射,然后您抱怨当您编写不安全的东西时类型系统会阻止您.你为什么要使用泛型可言

泛型(部分)是为了确保类型安全,然后您正在进行基于反射调度.这没有任何意义; 不要采取措施增加类型安全性,然后做出英勇的努力来解决它们.

显然你希望解决类型安全问题,所以根本不要使用泛型.只需创建一个ICommand接口和一个Handler接受命令的类,然后有一些机制来解决如何分派命令.

我不明白的是为什么有两种东西.如果要执行命令,那么为什么不简单地将执行逻辑放在命令对象上?

除了基于类型的这种笨重的字典查找之外,还可以使用其他设计模式.例如:

  • 命令处理程序可以有一个接受命令并返回布尔值的方法,处理程序是否可以处理此命令.现在你有一个命令处理程序列表,一个命令进来,你只是在列表中询问"你是我的处理程序吗?" 直到找到一个.如果O(n)查找太慢,则构建MRU缓存或记忆结果或某些此类事情,并且分摊的行为将得到改善.

  • 调度逻辑可以放入命令处理程序本身.给命令处理程序一个命令; 它要么执行它,要么递归,调用它的父命令处理程序.因此,您可以构建一个命令处理程序图表,以便在必要时将工作推迟到彼此.(这基本上是QueryService在COM中的工作方式.)