Let’s start with a definition:
“Events enable a class or object to notify other classes or objects when something of interest occurs. The class that sends (or raises) the event is called the publisher and the classes that receive (or handle) the event are called subscribers.” (source: https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/events/ )
The use of events in c# is pretty straightforward. However, same as every other programming technique, it may take some time for one to get used to and use effectively. Lately I have been revisiting the use of events in c#, trying to make sure that I use them properly in my projects. While doing so, I made a small case study, in the form of a console application, and decided to share it, in case it might help someone else as well.
The following code is a simple, yet fully functional, example of a proper use of events in c#. By “proper” I mean that it is a) functionally correct and b) it conforms to the common standards of the usage of the c# language. I took the time to comment the code in order to explain, the best I could, all of its functionality and turn it into a self – explanatory tutorial. So hopefully, by just reading through the code carefully, one may understand what is going on and why.
In this example, there are 10 number counters, each of which has a different (random) upper bound (MaxNum property). All of them count from zero up until their maximum number. As soon as any of them reaches their maximum, they raise the MaxNumReached event. The notifier object (instance of the Notifier class), which has been subscribed to all of the event publishers, responds to the event by printing useful information to the console.
Every time you run the example, the console output will be a bit different (because the maximum number of every number counter is set randomly). However, you should always receive 10 messages (one for each number counter). Here is a sample console output:
“Number Counter 2 has reached its maximum number of 10 at 1/4/2019 6:44:38 PM
Number Counter 6 has reached its maximum number of 11 at 1/4/2019 6:44:38 PM
[…] and so on…”
While making this case study and writing this blog post, two aspects of using events became more apparent to me, and I decided to mention them here, as an epilogue.
The benefit of using events:
Given this specific example, one may wonder: “why use events, at all?”. This is a legitimate question, in this simplistic scenario. After all, we could just provide a reference of the notifier to every one of our numberCounters, so that they could directly call the Notify method themselves, as soon as they reached their maximum number.
However, using the event pattern allows us to decouple the “event” of reaching a maximum number from a specific reference to the Notify method of the Notifier class. This is very useful in case we may want to expand the “effects” of our event. Imagine, for example, that we added another kind of event subscriber named EventDataSaver, that contains a method called SaveEventData that can save the event data to a file, instead of just printing them on the console.
By using the event pattern, it is very easy to do so, without altering the NumberCounter class, at all. We just make sure that the SaveEventData method has the same signature (return type and arguments) as our event’s delegate and then subscribe the EventDataSaver to the MaxNumReached event of all of our numberCounters.
Events vs Delegates
Events, in c#, are really similar to delegates. An event’s definition and declaration always relies on an underlying delegate which provides the signature for subscribing methods to it and its general use is quite similar to that of a delegate.
Many people are confused by their similarities (a simple google search might convince you about this fact) and I cannot say that I am 100% aware all of their differences. Apart from their formal use and their general technical differences, however, I have spotted one difference that seems quite important:
The fact that events can only be invoked from within the class that they were defined in. This property of events is a useful restriction, because it allows one to expose the event outside the class (to allow subscribers to subscribe to it), but still keep its usage protected from accidental misuse. In my opinion, this technical limitation is also an indicator of how events are supposed to be used.