Once again, in this series of posts I look at the parts of the .NET Framework that may seem trivial, but can help improve your code by making it easier to write and maintain. The index of all my past little wonders posts can be found here.
In the last two weeks, we examined the Action family of delegates (and delegates in general), and the Func family of delegates and how they can be used to support generic, reusable algorithms and classes.
So this week, we are going to look at a handy pair of delegates that can be used to eliminate the need for defining custom delegates when creating events: the EventHandler and EventHandler<TEventArgs> delegates.
Events and delegates
Before we begin, let’s quickly consider events in .NET. According to the MSDN:
An event in C# is a way for a class to provide notifications to clients of that class when some interesting thing happens to an object.
So, basically, you can create an event in a type so that users of that type can subscribe to notifications of things of interest. How is this different than some of the delegate programming that we talked about in the last two weeks? Well, you can think of an event as a special access modifier on a delegate. Some differences between the two are:
- Events are a special access case of delegates
- They behave much like delegates instances inside the type they are declared in, but outside of that type they can only be (un)subscribed to.
- Events can specify add/remove behavior explicitly
- If you want to do additional work when someone subscribes or unsubscribes to an event, you can specify the add and remove actions explicitly.
- Events have access modifiers, but these only specify the access level of those who can (un)subscribe
- A public event, for example, means anyone can (un)subscribe, but it does not mean that anyone can raise (invoke) the event directly.
- Events can only be raised by the type that contains them
- In contrast, if a delegate is visible, it can be invoked outside of the object (not even in a sub-class!).
- Events tend to be for notifications only, and should be treated as optional
- Semantically speaking, events typically don’t perform work on the the class directly, but tend to just notify subscribers when something of note occurs.
My basic rule-of-thumb is that if you are just wanting to notify any listeners (who may or may not care) that something has happened, use an event. However, if you want the caller to provide some function to perform to direct the class about how it should perform work, make it a delegate.
Declaring events using custom delegates
To declare an event in a type, we simply use the event keyword and specify its delegate type. For example, let’s say you wanted to create a new TimeOfDayTimer that triggers at a given time of the day (as opposed to on an interval). We could write something like this:
1 : public delegate void TimeOfDayHandler(object source, ElapsedEventArgs e);
2 : 3 : // A timer that will fire at time of day each day.
4 : public class TimeOfDayTimer : IDisposable 5 : {
6 : // Event that is triggered at time of day.
7 : public event TimeOfDayHandler Elapsed;
8 : 9 : // ...
10:
}
The first thing to note is that the event is a delegate type, which tells us what types of methods may subscribe to it. The second thing to note is the signature of the event handler delegate, according to the MSDN:
The standard signature of an event handler delegate defines a method that does not return a value, whose first parameter is of type Object and refers to the instance that raises the event, and whose second parameter is derived from type EventArgs and holds the event data. If the event does not generate event data, the second parameter is simply an instance of EventArgs. Otherwise, the second parameter is a custom type derived from EventArgs and supplies any fields or properties needed to hold the event data.
So, in a nutshell, the event handler delegates should return void and take two parameters:
- An object reference to the object that raised the event.
- An EventArgs (or a subclass of EventArgs) reference to event specific information.
Even if your event has no additional information to provide, you are still expected to provide an EventArgs instance. In this case, feel free to pass the EventArgs.Empty singleton instead of creating new instances of EventArgs (to avoid generating unneeded memory garbage).
The EventHandler delegate
Because many events have no additional information to pass, and thus do not require custom EventArgs, the signature of the delegates for subscribing to these events is typically:
1: // always takes an object and an EventArgs reference
2: public delegate void EventHandler(object sender, EventArgs e)
It would be insane to recreate this delegate for every class that had a basic event with no additional event data, so there already exists a delegate for you called EventHandler that has this very definition! Feel free to use it to define any events which supply no additional event information:
1: public class Cache
2: {
3: // event that is raised whenever the cache performs a cleanup
4: public event EventHandler OnCleanup;
5:
6: // ...
7: }
This will handle any event with the standard EventArgs (no additional information).
But what of events that do need to supply additional information? Does that mean we’re out of luck for subclasses of EventArgs? That’s where the generic for of EventHandler comes into play…
The generic EventHandler<TEventArgs> delegate
Starting with the introduction of generics in .NET 2.0, we have a generic delegate called EventHandler<TEventArgs>. Its signature is as follows:
1: public delegate void EventHandler<TEventArgs>(object sender, TEventArgs e)
2: where TEventArgs : EventArgs
This is similar to EventHandler except it has been made generic to support the more general case. Thus, it will work for any delegate where the first argument is an object (the sender) and the second argument is a class derived from EventArgs (the event data).
For example, let’s say we wanted to create a message receiver, and we wanted it to have a few events such as OnConnected that will tell us when a connection is established (probably with no additional information) and OnMessageReceived that will tell us when a new message arrives (probably with a string for the new message text).
So for OnMessageReceived, our MessageReceivedEventArgs might look like this:
1: public sealed class MessageReceivedEventArgs : EventArgs
2: {
3: public string Message { get; set; }
4: }
And since OnConnected needs no event argument type defined, our class might look something like this:
1 : public class MessageReceiver
2 : {
3 : // event that is called when the receiver connects with sender
4 : public event EventHandler OnConnected;
5 : 6 : // event that is called when a new message is received.
7
: public event EventHandler<MessageReceivedEventArgs> OnMessageReceived;
8 : 9 : // ...
10:
}
Notice, nowhere did we have to define a delegate to fit our event definition, the EventHandler and generic EventHandler<TEventArgs> delegates fit almost anything we’d need to do with events.
Sidebar: Thread-safety and raising an event
When the time comes to raise an event, we should always check to make sure there are subscribers, and then only raise the event if anyone is subscribed. This is important because if no one is subscribed to the event, then the instance will be null and we will get a NullReferenceException if we attempt to raise the event.
1: // This protects against NullReferenceException... or does it?
2: if (OnMessageReceived != null)
3: {
4: OnMessageReceived(this, new MessageReceivedEventArgs(aMessage));
5: }
The above code seems to handle the null reference if no one is subscribed, but there’s a problem if this is being used in multi-threaded environments.For example, assume we have thread A which is about to raise the event, and it checks and clears the null check and is about to raise the event. However, before it can do that thread B unsubscribes to the event, which sets the delegate to null. Now, when thread A attempts to raise the event, this causes the NullReferenceException that we were hoping to avoid!
To counter this, the simplest best-practice method is to copy the event (just a multicast delegate) to a temporary local variable just before we raise it. Since we are inside the class where this event is being raised, we can copy it to a local variable like this, and it will protect us from multi-threading since multicast delegates are immutable and assignments are atomic:
1: // always make copy of the event multi-cast delegate before checking
2: // for null to avoid race-condition between the null-check and raising it.
3: var handler = OnMessageReceived;
4:
5: if (handler != null)
6: {
7: handler(this, new MessageReceivedEventArgs(aMessage));
8: }
The very slight trade-off is that it’s possible a class may get an event after it unsubscribes in a multi-threaded environment, but this is a small risk and classes should be prepared for this possibility anyway. For a more detailed discussion on this, check out this excellent Eric Lippert blog post on Events and Races.
Summary
Generic delegates give us a lot of power to make generic algorithms and classes, and the EventHandler delegate family gives us the flexibility to create events easily, without needing to redefine delegates over and over. Use them whenever you need to define events with or without specialized EventArgs.