将派生类型委托存入基类型字典时的协变转换错误咨询
你遇到的问题核心是对泛型委托的逆变方向搞反了。你的HandleIntegrationEvent<in TR>用了in关键字(逆变),但逆变的规则是:能把接受基类的委托,赋值给接受派生类的委托变量,而不是反过来。
举个实际的例子:你可以把HandleIntegrationEvent<IBaseEvent>的实例赋值给HandleIntegrationEvent<UserCreatedEvent>的变量——毕竟能处理所有IBaseEvent类型的委托,肯定能处理具体的UserCreatedEvent。但反过来,专门处理UserCreatedEvent的委托,没办法处理其他IBaseEvent的派生类(比如假设存在OrderCreatedEvent),所以编译器不允许把HandleIntegrationEvent<TR>直接转换成HandleIntegrationEvent<IBaseEvent>。
下面是两种可行的解决办法:
方法一:用Lambda包装委托,适配类型
修改RegisterEventListener方法,把传入的listener包装成符合HandleIntegrationEvent<IBaseEvent>类型的委托,内部做类型校验后再调用原listener:
public void RegisterEventListener<TR>(string @event, HandleIntegrationEvent<TR> listener) where TR: IBaseEvent { _listeners.Add(@event, async (IBaseEvent e) => { if (e is TR concreteEvent) { await listener(concreteEvent); } // 可选:添加类型不匹配时的处理逻辑,比如抛出异常或静默忽略 }); }
这种方式既满足了字典的类型要求,又保证了类型安全——只有当传入的事件是TR类型时,才会执行原listener。
方法二:将字典存储为Delegate类型
把字典的类型改为IDictionary<string, Delegate>,直接存储原始的委托实例,触发事件时再做类型转换:
public class IntegrationBus { private readonly IDictionary<string, Delegate> _listeners = new Dictionary<string, Delegate>(); public void RegisterEventListener<TR>(string @event, HandleIntegrationEvent<TR> listener) where TR: IBaseEvent { _listeners.Add(@event, listener); } // 示例:触发事件的方法 public async Task TriggerEvent<T>(T @event) where T: IBaseEvent { if (_listeners.TryGetValue(@event.Name, out var listener)) { var typedListener = listener as HandleIntegrationEvent<T>; if (typedListener != null) { await typedListener(@event); } } } }
这种方式更灵活,不需要额外包装,但触发事件时要注意正确转换委托类型,避免类型不匹配的问题。
内容的提问来源于stack exchange,提问作者Paul Dreyer

