基于MessagePack构建消息服务时的泛型委托转换问题
你遇到的错误核心是泛型委托的逆变特性导致的:Action<T>是逆变委托,这意味着只有当T是某个类型的基类时,Action<T>才能转换为Action<基类>。但你的场景里TMessage是IMessage的子类,所以Action<TMessage>无法直接赋值给Action<IMessage>——毕竟Action<IMessage>可以接受任何IMessage类型的参数,但Action<TMessage>只能处理TMessage类型,直接转换会导致类型安全问题。
最简单的办法是把传入的Action<TMessage>包装成一个Action<IMessage>,在包装里做类型转换(因为我们知道订阅时的TMessage就是后续消息的类型,所以转换是安全的):
private Dictionary<Type, Action<IMessage>> subActions = new Dictionary<Type, Action<IMessage>>(); public void Subscribe<TMessage>(Action<TMessage> action) where TMessage : IMessage { // 包装委托,将IMessage转换为TMessage后执行原action subActions.Add(typeof(TMessage), msg => action((TMessage)msg)); } // 调用示例(假设你有一个发布方法) public void Publish(IMessage message) { if (subActions.TryGetValue(message.GetType(), out var action)) { action(message); } }
这个方案的好处是保持原有的Dictionary类型不变,逻辑简单直接,而且因为订阅时已经约束了TMessage是IMessage,所以强制转换不会有运行时风险。
Dictionary<Type, Delegate>存储 如果不想做委托包装,也可以直接用Delegate作为字典的值类型,存储原始的Action<TMessage>,在调用时再转换回对应的泛型委托:
private Dictionary<Type, Delegate> subActions = new Dictionary<Type, Delegate>(); public void Subscribe<TMessage>(Action<TMessage> action) where TMessage : IMessage { subActions.Add(typeof(TMessage), action); } public void Publish<TMessage>(TMessage message) where TMessage : IMessage { if (subActions.TryGetValue(typeof(TMessage), out var del)) { var action = (Action<TMessage>)del; action(message); } }
这个方案更贴近原始的委托类型,避免了包装层,但要求发布时必须明确指定消息的泛型类型(或者你可以在发布时通过反射获取类型并调用委托)。
如果你的消息服务需要支持多订阅者(同一个消息类型可以有多个处理方法),建议把字典的值改成List<Delegate>或者List<Action<IMessage>>,这样就能添加多个订阅逻辑了,比如:
private Dictionary<Type, List<Action<IMessage>>> subActions = new Dictionary<Type, List<Action<IMessage>>>(); public void Subscribe<TMessage>(Action<TMessage> action) where TMessage : IMessage { var messageType = typeof(TMessage); if (!subActions.ContainsKey(messageType)) { subActions[messageType] = new List<Action<IMessage>>(); } subActions[messageType].Add(msg => action((TMessage)msg)); } public void Publish(IMessage message) { if (subActions.TryGetValue(message.GetType(), out var actions)) { foreach (var action in actions) { action(message); } } }
内容的提问来源于stack exchange,提问作者Matthew Goulart

