在运行时转换为未知的派生类型?
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 });
}
我有这个命令总线实现,通常这样调用:
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 });
}