C#命令总线如何修改Invoke方法以支持运行时未知派生类型Command调用
问题根源
转换失败的核心原因是泛型参数的编译时绑定和运行时实际类型不匹配:
- 调用
bus.Invoke<Command>(parsedCommand)时,泛型参数T在编译阶段就被固定为Command基类 - 你获取到的处理器实例是
ICommandHandler<FocusCommand>类型,它和ICommandHandler<Command>不存在继承关系,强制转换会直接返回null,后续调用就会抛出空引用异常。
解决方案
以下两种实现都可以解决动态调用的问题,你可以根据项目场景选择:
方案1:新增非泛型Invoke重载,通过反射调用泛型逻辑
这个方案对现有代码侵入最小,完全兼容原来的泛型调用逻辑,适合中大型生产项目使用:
public class Bus { // 原有泛型Invoke保留,供编译时已知命令类型的场景调用 public void Invoke<T>(T command) where T : Command { object handlerInstance = GetHandlerInstanceForCommand(command); (handlerInstance as ICommandHandler<T>).Handle(command); } // 新增非泛型重载,供运行时才知道具体命令类型的场景调用 public void Invoke(Command command) { // 获取命令的运行时实际类型 Type commandType = command.GetType(); // 构造闭合的泛型Invoke方法:Invoke<实际命令类型>(command) var genericInvokeMethod = typeof(Bus) .GetMethod(nameof(Invoke), 1, new[] { commandType }) ?.MakeGenericMethod(commandType); if (genericInvokeMethod == null) throw new InvalidOperationException($"找不到对应命令{commandType.Name}的调用方法"); // 执行泛型方法 genericInvokeMethod.Invoke(this, new object[] { command }); } // 原有GetHandlerInstanceForCommand保持不变 private object GetHandlerInstanceForCommand<T>(T command) where T : Command { if (command.GetType() == typeof(FocusCommand)) return new FocusHandler(); else throw new Exception(); } }
调用时不需要显式指定泛型参数,编译器会自动匹配非泛型重载:
// 旧写法(走泛型重载,T为Command,会报错) // bus.Invoke<Command>(parsedCommand); // 新写法(自动走非泛型重载,正常运行) bus.Invoke(parsedCommand);
提示:如果是高频调用场景,可以把构造好的泛型方法缓存到
ConcurrentDictionary中,key为命令类型,避免每次调用都反射查方法,优化后性能和编译时调用几乎无差异。
方案2:用dynamic动态绑定
如果项目对性能要求不高,想要最小改动修复问题,可以用dynamic做运行时绑定,只需要修改一行代码:
public void Invoke<T>(T command) where T : Command { object handlerInstance = GetHandlerInstanceForCommand(command); // 用dynamic做运行时绑定,不需要强制转换 (handlerInstance as dynamic).Handle(command as dynamic); }
这个方案修改量极小,不需要改动其他逻辑,适合小型项目快速修复问题。
内容的提问来源于stack exchange,提问作者L. Berger
相关产品推荐
相关产品推荐

