C#泛型委托类型转换错误:无法将Action<T>转为Action<BaseEvent>
泛型事件处理的类型转换错误及无强制转换实现方案
现有代码
CycleManager类
public class CycleManager { private ConcurrentQueue<BaseCommand> m_Commands = new ConcurrentQueue<BaseCommand>(); private EventContainer<BaseEvent> m_Container = new EventContainer<BaseEvent>(); public void RunCommand(BaseCommand command) { m_Commands.Enqueue(command); } public void WaitForEvent<T>(Action<T> handler) where T : BaseEvent { m_Container.Add(handler); } }
EventContainer类
public class EventContainer<TEvent> where TEvent : BaseEvent { private Dictionary<Type, Action<TEvent>> m_eventHandlers = new Dictionary<Type, Action<TEvent>>(); public void Add(Action<TEvent> handler) { m_eventHandlers.Add(typeof(TEvent), handler); } }
错误信息
在执行m_Container.Add(handler);时出现编译错误:
Argument 1: cannot convert from 'System.Action
' to 'System.Action<CycleManagerTesting.Models.BaseEvent>'
期望的调用方式
希望实现无需强制转换事件类型的调用,直接访问子类事件的属性:
var c = new CycleManager(); c.WaitForEvent<DoorOpenedEvent>(e => { // 直接操作e.DoorID,无需强制转换 Console.WriteLine($"门ID:{e.DoorID}"); });
现有不满意的方案
若修改EventContainer的Add方法参数为Action<BaseEvent>,虽然能编译通过,但调用时必须强制转换事件类型,代码冗余:
var c = new CycleManager(); c.WaitForEvent<DoorOpenedEvent>(e => { var realEvent = (DoorOpenedEvent)e; // 操作realEvent.DoorID });
相关基础类定义:
public class DoorOpenedEvent : BaseEvent { public string DoorID { get; set; } public string Status { get; set; } } public class BaseEvent { } public class BaseCommand { }
解决方案
调整EventContainer的实现,改用Dictionary<Type, Delegate>存储不同类型的事件处理器,同时提供泛型的Add方法,从根源避免类型转换问题:
修改后的EventContainer类
public class EventContainer { private readonly Dictionary<Type, Delegate> _eventHandlers = new Dictionary<Type, Delegate>(); public void Add<TEvent>(Action<TEvent> handler) where TEvent : BaseEvent { var eventType = typeof(TEvent); // 支持同一事件类型添加多个处理器 if (_eventHandlers.ContainsKey(eventType)) { _eventHandlers[eventType] = Delegate.Combine(_eventHandlers[eventType], handler); } else { _eventHandlers.Add(eventType, handler); } } // 补充事件触发方法,用于分发事件 public void Trigger<TEvent>(TEvent eventInstance) where TEvent : BaseEvent { if (_eventHandlers.TryGetValue(typeof(TEvent), out var handler)) { ((Action<TEvent>)handler)?.Invoke(eventInstance); } } }
修改后的CycleManager类
public class CycleManager { private ConcurrentQueue<BaseCommand> m_Commands = new ConcurrentQueue<BaseCommand>(); private EventContainer m_Container = new EventContainer(); public void RunCommand(BaseCommand command) { m_Commands.Enqueue(command); } public void WaitForEvent<T>(Action<T> handler) where T : BaseEvent { m_Container.Add(handler); } // 可选:对外提供触发事件的方法(根据业务场景调整) public void TriggerEvent<T>(T eventInstance) where T : BaseEvent { m_Container.Trigger(eventInstance); } }
验证调用
现在可以直接按期望的方式调用,无需任何强制转换:
var c = new CycleManager(); c.WaitForEvent<DoorOpenedEvent>(e => { Console.WriteLine($"门{e.DoorID}已打开,状态:{e.Status}"); }); // 测试触发事件 c.TriggerEvent(new DoorOpenedEvent { DoorID = "D001", Status = "Open" });
原理说明
原代码报错是因为Action<T>(T为BaseEvent子类)无法直接转换为Action<BaseEvent>——Action委托是逆变类型,仅允许从基类委托转换为子类委托,反之则不成立。修改后的EventContainer通过泛型Add方法直接存储对应类型的Action<TEvent>,触发时再将Delegate转换为对应类型的委托调用,彻底避免了类型转换的困扰。
内容的提问来源于stack exchange,提问作者Izikon
相关产品推荐
相关产品推荐

