字典上 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方法中进行强制转换,但是此时你知道强制转换是正确的。