C#泛型接口转换报错:无法将IEventListener<TEvent>转为IEventListener<EventBase>
C#泛型逆变接口转换失败的原因与修复方案
问题原因:逆变方向理解错误
你标记了in关键字的IEventListener<TEvent>确实是逆变接口,但你搞反了它的合法转换方向:
- 逆变的规则是:如果
TDerived是TBase的派生类,那么IEventListener<TBase>可以安全转换为IEventListener<TDerived>(因为接口只接受TEvent作为输入,基类的输入处理逻辑可以兼容派生类)。 - 你现在试图将
IEventListener<TEvent>(TEvent是EventBase的子类)转换为IEventListener<EventBase>,这是逆变的反向操作,完全不符合逆变的设计逻辑,因此编译报错,强制转换会触发运行时异常。
你的字典_subscriptions定义为Dictionary<Type, List<IEventListener<EventBase>>>,但要存入的是IEventListener<TEvent>实例,当TEvent是EventBase的派生类时,这个转换在逆变规则下是不允许的。
修复方案
方案1:新增非泛型基接口(推荐)
添加一个非泛型的IEventListener接口作为泛型接口的基类,让字典存储统一的非泛型实例,既解决存储问题,又保留类型安全:
namespace Game { public static class EventSystem { public class EventBase { } // 新增非泛型监听器基接口 public interface IEventListener { } public interface IEventListener<in TEvent> : IEventListener where TEvent : EventBase { void OnEvent(TEvent ev); } // 字典值类型改为List<IEventListener> private static Dictionary<Type, List<IEventListener>> _subscriptions = new(); public static void AddListener<TEvent>(IEventListener<TEvent> listener) where TEvent : EventBase { if (!_subscriptions.ContainsKey(typeof(TEvent))) { _subscriptions.Add(typeof(TEvent), new()); } var list = _subscriptions[typeof(TEvent)]; list.Add(listener); } public static void RemoveListener<TEvent>(IEventListener<TEvent> listener) where TEvent : EventBase { if (_subscriptions.ContainsKey(typeof(TEvent))) { var list = _subscriptions[typeof(TEvent)]; list.Remove(listener); } } // 补充事件触发示例(可选) public static void TriggerEvent<TEvent>(TEvent ev) where TEvent : EventBase { if (_subscriptions.TryGetValue(typeof(TEvent), out var listeners)) { foreach (var listener in listeners.Cast<IEventListener<TEvent>>()) { listener.OnEvent(ev); } } } } }
方案2:使用object存储(类型安全稍弱)
如果不想新增接口,也可以将字典值类型改为List<object>,利用引用相等性完成添加/移除操作,触发事件时再转换为对应的泛型接口:
namespace Game { public static class EventSystem { public class EventBase { } public interface IEventListener<in TEvent> where TEvent : EventBase { void OnEvent(TEvent ev); } private static Dictionary<Type, List<object>> _subscriptions = new(); public static void AddListener<TEvent>(IEventListener<TEvent> listener) where TEvent : EventBase { if (!_subscriptions.ContainsKey(typeof(TEvent))) { _subscriptions.Add(typeof(TEvent), new()); } var list = _subscriptions[typeof(TEvent)]; list.Add(listener); } public static void RemoveListener<TEvent>(IEventListener<TEvent> listener) where TEvent : EventBase { if (_subscriptions.ContainsKey(typeof(TEvent))) { var list = _subscriptions[typeof(TEvent)]; list.Remove(listener); } } public static void TriggerEvent<TEvent>(TEvent ev) where TEvent : EventBase { if (_subscriptions.TryGetValue(typeof(TEvent), out var listeners)) { foreach (var listener in listeners.Cast<IEventListener<TEvent>>()) { listener.OnEvent(ev); } } } } }
总结
核心错误是对逆变方向的误解:逆变允许基类泛型接口向派生类泛型接口转换,而非你尝试的反向转换。通过新增非泛型基接口的方案,既能解决存储兼容问题,又能保证类型安全,是这类事件系统的标准实现方式。
内容的提问来源于stack exchange,提问作者papirosnik
相关产品推荐
相关产品推荐

