在运行时转换为未知的派生类型?

Casting to unknown derived type at runtime?

我有这个命令总线实现,通常这样调用:

bus.Invoke(new FocusCommand());

这工作正常,但现在有一种情况是要调用的命令是动态的并且会抛出错误:

// Command to invoke is resolved at runtime.
Command parsedCommand = ParseCommand(input);
bus.Invoke(parsedCommand);

实施的简化版本如下所示:

using System;
using System.Diagnostics;

public class Command
{
}

public class FocusCommand : Command
{
}

public interface ICommandHandler<TCommand> where TCommand : Command
{
  void Handle(TCommand command);
}

public class FocusHandler : ICommandHandler<FocusCommand>
{
  public void Handle(FocusCommand command)
  {
    Debug.WriteLine("FocusHandler was called.");
  }
}

public class Bus
{
  // Sends command to appropriate command handler.
  public void Invoke<T>(T command) where T : Command
  {
    object handlerInstance = GetHandlerInstanceForCommand(command);

    (handlerInstance as ICommandHandler<T>).Handle(command); // <--- Error is thrown here in 2nd case.
  }

  // Returns an instance of the corresponding handler for a command.  In the real
  // application, this gets the handler instance from the DI framework using
  // `Provider.GetRequiredService`, which always returns an `object`.
  private object GetHandlerInstanceForCommand<T>(T command) where T : Command
  {
    if (command.GetType() == typeof(FocusCommand))
      return new FocusHandler();
    else
      throw new Exception();
  }
}

static class Program
{
  /// <summary>
  ///  The main entry point for the application.
  /// </summary>
  [STAThread]
  static void Main()
  {
    Debug.WriteLine("Application started");

    Bus bus = new Bus();
    bus.Invoke<FocusCommand>(new FocusCommand()); // Is called successfully.

    Command parsedCommand = ParseCommand("FocusCommand");
    bus.Invoke<Command>(parsedCommand); // Throws error.
  }

  // Returns different commands based on input.
  private static Command ParseCommand(string input)
  {
    if (input == "FocusCommand")
      return new FocusCommand();
    else
      throw new Exception();
  }
}

调用 bus.Invoke<Command>(parsedCommand) 时,在 Bus.Invoke 内转换 handlerInstance as ICommandHandler<T> 失败。

如何更改 Bus.Invoke 以使用派生的 Command 类型未知的命令?

这与 covariance and contravariance 有关。

转换失败的原因是,如果您被允许执行您正在尝试执行的操作,您将能够将不兼容的类型传递给您的处理程序。例如,您的方法将允许这样:

ICommandHandler<Command> focusHandler = new FocusHandler();
Command closeCommand = new CloseCommand();
focusHandler.Handle(closeCommand);

处理编译时未知类型的一种方法是使用反射。您提到了 Provider.GetRequiredService,所以我假设您使用的是 Microsoft.Extensions.DependencyInjection。您可以执行以下操作:

  • 将所有处理程序注册为其实际类型
  • 使用反射从实际命令类型创建实际处理程序类型
  • 使用该类型从 DI 获取处理程序
  • 使用反射调用处理程序

像这样注册您的处理程序:

services.AddTransient<ICommandHandler<FocusCommand>, FocusHandler>();
services.AddTransient<ICommandHandler<CloseCommand>, CloseHandler>();

像这样实现您的 Invoke 方法:

public void Invoke(Command command)
{
    Type commandType = command.GetType();
    Type handlerType = typeof(ICommandHandler<>).MakeGenericType(commandType);
    var handler = serviceProvider.GetService(handlerType);
    var mi = handlerType.GetMethod("Handle", 0, new[] { commandType });
    mi.Invoke(handler, new[] { command });
}