字典上 C# 委托的逆变问题
contravariant problems with C# delegates on dictionaries
一段时间以来,我一直在为游戏开发不同的事件系统,在这些系统中,侦听器接收到一个通用事件类型对象,并且必须通过开关或类似的方式区分其真实类型,然后转换为正确的子class 事件。
经过不同的方法后,我能够使用如下结构(简化)摆脱 switch-case:
public class Event {}
public class EventA : Event {}
public class EventB : Event {}
public delegate void HanlderDelegate(Event ev);
public class EventManager
{
Dictionary<Type, HanlderDelegate> delegateMap = new Dictionary<Type, HanlderDelegate>();
public void Dispatch(Event ev)
{
if (delegateMap.ContainsKey(ev.GetType()))
{
delegateMap[ev.GetType()].Invoke(ev);
}
}
public void Register<T>(HanlderDelegate handler) where T : Event
{
delegateMap.Add(typeof(T), handler);
}
}
public class EventListener
{
public EventListener(EventManager evtMgr)
{
evtMgr.Register<EventA>(this.HandleEventA);
evtMgr.Register<EventB>(this.HandleEventB);
}
public void HandleEventA(Event ev)
{
EventA evA = (EventA)ev;
//... do stuff
}
public void HandleEventB(Event ev)
{
EventB evB = (EventB)ev;
//... do stuff
}
}
我对这种方法很满意,但我仍然发现每种方法中的转换都可以改进。我试图让代表更通用
public delegate void HanlderDelegate<T>(T ev) where T : Event;
这样听众就可以直接实现 public void HandleEvent(EventA ev)
和 public void HandleEvent(EventB ev)
并注册它们。
但是当然,EventManager class 中的字典应该存储指向 HanlderDelegate<Event>
的指针,这就是问题开始的地方,我无法将 HanlderDelegate<EventA>
转换为 HanlderDelegate<Event>
来存储它们,并同时以另一种方式转换它来调用它们。
有办法实现吗?我知道编译器会允许奇怪的东西,但我知道它并且可以通过代码控制 EventB 没有被错误地转换为 EventA 等等。
提前致谢!
您可以使委托和 Dispatch
方法通用,并将处理程序存储为 Delegate
而不是 HandlerDelegate<T>
:
delegate void HandlerDelegate<TEvent>(TEvent ev) where TEvent : Event;
class EventManager
{
Dictionary<Type, Delegate> delegateMap = new Dictionary<Type, Delegate>();
public void Dispatch<TEvent>(TEvent ev) where TEvent : Event
{
Delegate d;
if (delegateMap.TryGetValue(typeof(TEvent), out d))
{
var handler = (HandlerDelegate<TEvent>)d;
handler(ev);
}
}
public void Register<TEvent>(HandlerDelegate<TEvent> handler) where TEvent : Event
{
delegateMap.Add(typeof(TEvent), handler);
}
}
当然还是要在Dispatch
方法中进行强制转换,但是此时你知道强制转换是正确的。
一段时间以来,我一直在为游戏开发不同的事件系统,在这些系统中,侦听器接收到一个通用事件类型对象,并且必须通过开关或类似的方式区分其真实类型,然后转换为正确的子class 事件。
经过不同的方法后,我能够使用如下结构(简化)摆脱 switch-case:
public class Event {}
public class EventA : Event {}
public class EventB : Event {}
public delegate void HanlderDelegate(Event ev);
public class EventManager
{
Dictionary<Type, HanlderDelegate> delegateMap = new Dictionary<Type, HanlderDelegate>();
public void Dispatch(Event ev)
{
if (delegateMap.ContainsKey(ev.GetType()))
{
delegateMap[ev.GetType()].Invoke(ev);
}
}
public void Register<T>(HanlderDelegate handler) where T : Event
{
delegateMap.Add(typeof(T), handler);
}
}
public class EventListener
{
public EventListener(EventManager evtMgr)
{
evtMgr.Register<EventA>(this.HandleEventA);
evtMgr.Register<EventB>(this.HandleEventB);
}
public void HandleEventA(Event ev)
{
EventA evA = (EventA)ev;
//... do stuff
}
public void HandleEventB(Event ev)
{
EventB evB = (EventB)ev;
//... do stuff
}
}
我对这种方法很满意,但我仍然发现每种方法中的转换都可以改进。我试图让代表更通用
public delegate void HanlderDelegate<T>(T ev) where T : Event;
这样听众就可以直接实现 public void HandleEvent(EventA ev)
和 public void HandleEvent(EventB ev)
并注册它们。
但是当然,EventManager class 中的字典应该存储指向 HanlderDelegate<Event>
的指针,这就是问题开始的地方,我无法将 HanlderDelegate<EventA>
转换为 HanlderDelegate<Event>
来存储它们,并同时以另一种方式转换它来调用它们。
有办法实现吗?我知道编译器会允许奇怪的东西,但我知道它并且可以通过代码控制 EventB 没有被错误地转换为 EventA 等等。
提前致谢!
您可以使委托和 Dispatch
方法通用,并将处理程序存储为 Delegate
而不是 HandlerDelegate<T>
:
delegate void HandlerDelegate<TEvent>(TEvent ev) where TEvent : Event;
class EventManager
{
Dictionary<Type, Delegate> delegateMap = new Dictionary<Type, Delegate>();
public void Dispatch<TEvent>(TEvent ev) where TEvent : Event
{
Delegate d;
if (delegateMap.TryGetValue(typeof(TEvent), out d))
{
var handler = (HandlerDelegate<TEvent>)d;
handler(ev);
}
}
public void Register<TEvent>(HandlerDelegate<TEvent> handler) where TEvent : Event
{
delegateMap.Add(typeof(TEvent), handler);
}
}
当然还是要在Dispatch
方法中进行强制转换,但是此时你知道强制转换是正确的。