Thursday, February 25, 2021 17:03

Table of contents >> Delegates, Lambda Expressions, Events > EventHandler, sender and EventArgs

EventHandler, sender and EventArgs

You noticed that throughout pretty much all the previous lessons where I discussed events, I have been using Action as the delegate type for the event. Obviously, you can use whatever delegate type you’d like, but in the vast majority of the cases, by convention (and only by convention!) programmers use a delegate type named EventHandler. Let’s take an example of how we used events up until now:

We have a class called TrainSignal, which hosts an event named TrainsAreComing, and a public method named TriggerTrainSignal(), which we use to invoke the event and call any subscriber method. Now, I will change the delegate type of the event from Action to EventHandler:

Nothing really changed, except for the fact that the compiler will now underline our event invocation with a squiggly red line, denoting an error: Error CS7036 There is no argument given that corresponds to the required formal parameter ‘sender’ of ‘EventHandler’. In order to understand what this error means and why is it showing up in the first place, we only have to navigate to the declaration of EventHandler, and look at its signature:

C# EventHandler signatureIt is easy to notice that it is just a delegate, but it takes two arguments, unlike Action, which took none: an object named sender and an EventArgs named e; this is the only reason why we have an error, the compiler expects us to provide these two parameters when we invoke our event.

The first parameter is self-explanatory, we already learned about object variable type. But EventArgs is new to us. Let’s navigate to its definition too:

C# EventArgs signatureYou can really see that this class is mostly empty: it only keeps a static reference to itself, but other than that, it’s empty. So, really, don’t let all this scare you, it’s nothing to it, and I’ll show it in a moment.

Returning to our original code, we know that the event invocation expects two parameters, because that’s what EventHandler expects. The first parameter, sender, it is always the object that is invoking the event, while the second parameter, e, is just some extra details about the event (which can be anything), if you wish to pass them.

To correct our error, let’s pass these two parameters. Since I just explained that the first parameter is always the object that is invoking the event, we will use the this C# keyword as the sender parameter, because this refers to the TrainSignal class instance we are currently using, and TrainSignal IS the object that is invoking the TrainsAreComing event. As for the second parameter, since I said that we can use it to pass additional information, we could just pass null, because at this point we don’t really have any additional information we could pass, but passing null as parameters is generally a very bad idea, for various reasons (passing null IS NOT same thing as passing nothing at all). We could also pass a new instance of EventArgs, like this: TrainsArecoming(this, new EventArgs());, but if you remember, we just had a look at the EventArgs class, and we noticed it has a static instance of itself, called Empty, which we can use here, to indicate we do not have any additional information to pass: TrainsArecoming(this, EventArgs.Empty);. EventArgs.Empty is basically just a newed up empty EventArgs for us to use.

However, let’s also use this second parameter, let’s actually pass some additional information, just so you understand how this works, and why is it useful. For start, let’s create an enum named TrainSignalType, with two elements, AudioSignal and VisualSignal, and then add a property of this enumeration type to our TrainSignal class, named SignalType:

Next, let’s declare a few instances of TrainSignal class, and give them each some TrainSignalType values:

Next, we need a method that can handle the event, or, as we already learned, a subscriber method that gets called when the event is invoked:

You can notice that unlike the example in the previous lessons, StopTheCar() now also takes two parameters of the same type as our event delegate, because, as we learned, we can only assign methods with same signature as the delegate to which we are assigning them. Some of you might also notice the fact that when we are invoking TrainsAreComing event, we are passing this as the sender parameter, and this in that context refers to a TrainSignal type. However, in our event handler method StopTheCar(), we are using object as the type of sender. And this is only because we can’t really know what type the class that will invoke the event will be, so, we are using object because object is the parent of all other types in .NET, they all inherit from it, therefor, even if the compile type of sender is object, the runtime type will be converted to TrainSignal. For this reason, we are allowed to cast our sender parameter back to the type it actually is (if we know beforehand what this type is), in our case, TrainSignal:

Obviously, we can also access the SignalType properties of these TrainSignal instances, after we subscribe our StopTheCar() method to the TrainsAreComing event of any of the two TrainSignal instances, and we call the TriggerTrainSignal() method, that is actually triggering the event:

The output would look like this:

So, this could be a way of using some arbitrary data (the signal type) of our TrainSignal instances. Another way we can do this is by using the second parameter of EventHandler, which I said it can be empty, if we don’t need to pass any additional information (using EventArgs.Empty). Let’s just assume for the sake of this lesson that we do want to pass this extra information, let’s say we want to pass exactly this SignalType property of our instances. If you would take a look at EventHandler signature, you will notice that it comes in two forms, one of which is a generic form:

C# EventHandler generic versionThat means that I can modify my declaration of EventHandler to pass my own version of EventArgs type, as opposed to using the default EventHandler declaration, when EventArgs is used. I will declare another class named TrainSignalEventArgs (by convention, a custom EventArgs type’s name is suffixed with the EventArgs word), and I will use this class as the EventArgs parameter of EventHandler generic version:

You can notice that TrainSignalEventArgs is just a simple class, empty for now. Be aware that the signature of the generic form of EventHandler that I showed above is taken from the .NET version 4.5. Before version 4.5, its signature looked like this:
The where TEventArgs : EventArgs part is basically forcing us to use only classes that inherit from EventArgs as parameters for the generic EventHandler. This means that before .NET 4.5, I would have been forced to declare my TrainSignalEventArgs like this:
Next, I changed my TrainsAreComing event declaration to use the generic form of EventHandler:
This forced me to also change the call where I invoke the event, and pass a TrainSignalEventArgs object as EventArgs parameter:
Finally, I also needed to change the signature of the event handler method, to also accept a TrainSignalEventArgs parameter:
The next step is to move the SignalType property from TrainSignal class inside TrainSignalEventArgs class, because we will not be accessing the signal type of the instance that triggered the event from a property of the instance itself, but through the e parameter of the EventArgs parameter of the EventHandler:
You can now observe that TrainSignal no longer has a property named SignalType, because this property was moved to TrainSignalEventArgs:
Since we are using a TrainSignalEventArgs instance as the generic type for EventHandler, when we invoke our event, we need to pass a TrainSignalEventArgs instance as a parameter of the invocation, and we can set the SignalType property for this instance here:
Finally, because we passed a TrainSignalEventArgs parameter as EventArgs, and because we set the SignalType for that parameter, we can now access it inside our event handler method, through the e parameter:
Of course, we could have any number of properties, methods, fields, whatever, inside our TrainSignalEventArgs. This could be really useful in some situations, and the most eloquent example is when using the MouseMove event. This event is triggered whenever the user moves the mouse, and in its case, the e parameter (which is of type MouseEventArgs, a custom form of EventArgs, just like our TrainSignalEventArgs) has a very useful method, GetPosition(), which gives the user the coordinates of the mouse cursor on the screen, if they ever need it. Another example is the event KeyUp, which occurs whenever the user presses a keyboard key: in this case, the e parameter is of type KeyEventHandler, and contains a property named Key, which tells the user what key was pressed.

So, there you have it, EventArgs, in its generic form, allows us to pass useful information, in case we decide we need it, and this is one of the reasons EventHandler is the most common type of delegate used for events.



Tags: , , , , ,

Leave a Reply

Follow the white rabbit