I have been developing for a while different event systems for games where the listener received a generic event-type object and had to distinguish its real type with a switch or similar and then cast to the correct subclass event.
After different aproaches I was able to get rid of the switch-case using a structure as follows (simplified):
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
}
}
I'm quite happy with this approach, but I still find the casting in each method improvable. I tried to make the delegate more generic
public delegate void HanlderDelegate<T>(T ev) where T : Event;
so listeners could directly implement public void HandleEvent(EventA ev)
and public void HandleEvent(EventB ev)
and register them.
But of course, the dictionary in the EventManager class should store pointers to HanlderDelegate<Event>
, and there's where problems begin, I cannot cast HanlderDelegate<EventA>
to HanlderDelegate<Event>
in order to store them, and at the same time casting it the other way to invoke them.
Is there a way to achieve this? I know the compiler would allow weird stuff, but I'm aware of it and can control by code that EventB is not incorrectly being casted to EventA and so on.
Thanks in advance!
You could make the delegate and the Dispatch
method generic, and store the handlers as Delegate
rather than 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);
}
}
Of course, you still have to cast in the Dispatch
method, but at this point you know that the cast is correct.