Generic types and inheritance
Hello,
I am trying to implement a basic event bus to have a single point for subscribing and publishing events and keeping clients anonymous from each others.
However I am having trouble defining type genericity for my event objects.
Attached file is the complete code extract.
The issue is raised at line 53 in
subscribe()
method when I try to add the event handler to my collection.
I am getting this error :
Argument 1: cannot convert from 'TEventHandler' to 'Events.IEventHandler<Events.Event>' (CS1503)
I don't know if I should look around improving the type constraints or if my _subjects
dictionary should be typed differently.
Any help would be much appreciated 🙂
----
Full code extract below :5 Replies
Your list contains
IEventHandler<Event>
instances. But you're trying to add a TEventHandler
do it, which is an IEventHandler<TEvent>
, not an IEventHandler<Event>
.
Put more simply, you can't do EventHandler<Event> handler = new EventHandler<UserCreatedEvent>()
.
The reason is that HandleEvent
method. Your EventHandler<UserCreatedEvent>
class has a HandleEvent(UserCreatedEvent evt)
method -- i.e. it just takes UserCreatedEvent
instances. But an EventHandler<Event>
class has a HandleEvent(Event evt)
method -- i.e. it can take any Event
type.
So if you do EventHandler<Event> handler = new EventHandler<UserCreatedEvent>()
, you'd be able to do handler.HandleEvent(new Event())
-- you'd be able to pass an Event
to a method which only accepts UserCreatedEvent
instances.
(Also, with event aggregators, it's much more common to pass an instance fo the Subscribe
method, rather than creating the instance yourself. This lets the user create the handler themselves, pass ctor parameters, configure it, etc)
If you want some keywords to read up more on this, search for "covariance" and "contravariance". You'll come across the in
and out
keywords, but they don't actually help this casebut there is a constraint for
TEvent
to inherit from Event
Please re-read my first message. Yes there is, but the problem is the opposite way around
In other words, there's nothing stopping you from doing
_subjects[typeof(UserCreatedEvent)][0].HandleEvent(new Event())
If that compiler error wasn't there, the above code would be allowed by the type system, but would obviously be bad, because an IEventHandler<UserCreatedEvent>
is being passed an Event
instance
One quick workaround is to keep a list of "things which can invoke IEventHandlers
", rather than the IEventHandlers
themselves. For example, a List<Action<Event>>
, and then do:
That works because it moves the problem to a runtime cast (the (TEvent)evt
), so if you do _subjects[typeof(UserCreatedEvent)][0].Invoke(new Event())
, the (TEvent)evt
cast will fail to cast it to a UserCreatedEvent
instance that the handler needs, and you'll get a runtime InvalidCastException
ok, I think I get a grasp on what you are explaining
I am still missing some precise understanding but I will try to document myself based on your answers
thank you for your time
Cool. Yeah everyone hits this sooner or later. In a simple case it's fairly easy to understand, but it tends to bite when the problems are hidden behind a few confounding layers, so if it's your first time seeing it, it can be hard to figure out what's going on
But start with understanding why
EventHandler<Event> handler = new EventHandler<UserCreatedEvent>()
isn't allowed, gets your head solidly around that, then work from there