c# - 在运行时转换为未知的派生类型?
问题描述
我有这个命令总线实现,通常这样调用:
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)
被调用时,强制转换handlerInstance as ICommandHandler<T>
失败Bus.Invoke
。
如何Bus.Invoke
更改以使用派生Command
类型未知的命令?
解决方案
这与协变和逆变有关。
强制转换失败的原因是,如果您被允许做您想做的事情,您将能够将不兼容的类型传递给您的处理程序。例如,您的方法将允许这样做:
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 });
}