A common .NET-related design question that I get is when to define events in your application code; (usually worded something like this, "when would I ever do this?") As a trend, I have noticed that developers tend to become familiar with consuming events defined by the class library after having used C# or VB.Net for just a short amount of time. However, making the leap from writing event-consumers to creating your own event-producers is a tougher challenge.
Events are meant to represent notifications in your code. But ultimately the code being notified is represented as a method, and the code raising the notification is calling that method. So how then do events differ from a typical method-call? Understanding this is key to understanding when to define your own events.
MethodB is a non-virtual method (more on virtuals in a bit) being called by MethodA:
MethodA --> MethodB – call direction and dependency direction are the same.
In the typical non-virtual method-call relationship, the dependency relationship is in the direction of the call. Said another way, at compile time, MethodA’s code depends on MethodB’s code, and must know important information about MethodB, such as what its signature is, what class or struct defines the method, whether the method is instance or static, and what assembly the code is packaged in.
With events, the dependency relationship is reversed:
Event-Raiser --> Event-Receiver – call direction
Event-Raiser <-- Event-Receiver – dependency direction
This reversal of dependency has the important impact of allowing calling code to be written without prior knowledge of (or a dependency on) the code being called. This is very useful for library code, which must pre-exist the application code that uses the library. Thus the substantial number of events defined in the .NET Framework class library.
But what of events raised from your application code? Well, this reversing of the dependency relationship can be useful for apps as well. An example that I saw today (which inspired this blog entry in the first place), was a user control in an ASP.Net application. Without going into gory details, the user control had taken dependencies on the pages which contained the control (not an easy thing to accomplish, since ASP.Net disallows cyclic relationships between auto-generated portions of the application). In essence, the control was saying "if I am embedded in page A, do this, if I am embedded in page B do that," and so on.
In defense of the code's author, the user control didn't start out this way, but over time it began to be used in more places, and yet at a certain point in the logic, it needed to pass control on to page-specific processing.
This is an example where the reversed dependency of an event is a more natural fit to the already established dependency between various pages and a user control. And so, by introducing an event in the user control, now each page was able to perform its own page-specific processing, in response to an event raised by the user-control. The pages registered for the event in each case, and now the control was responsible only for the notifying part of the code. The encapsulation improvements in this minor change, in this case, were far-reaching, because now the user control no longer needed to grovel around in the pages' controls collection to gather necessary state.
In both ASP.Net and Windows Forms user-controls are an area where applications can benefit from implementing their own events (properties are useful here too, incidentally).
Alright, so reversal of the dependency relationship in a method call is one reason for events. Events, though, are not the only object oriented-idiom with this attribute. Interface methods as well as class-defined virtual methods exhibit this reverse dependency. And so, the next question is how do events and virtual methods differ. Two noteworthy differences are somewhat related, which are differences in discovery and impact on your derivation lineage. Let's start with discovery.
Events have a built-in registration and un-registration mechanism. In fact, given that the actual call is implemented by the lower-level delegate, you could make the argument that events offer nothing but a registration and un-registration mechanism. This allows for dynamic addition and removal of a delegate in the callback list of some notifying code. The add and remove mechanism is really just a method-pair, but the method pair is well defined, which has a number of useful effects. One is that the definer of the event-raising code can all but completely ignore the registration details here, and just let the compiler do the work, if they want to. Another is that designers and intellisense can consistently automate the wiring of handlers to arbitrarily defined events. (This automatability extends to the COM interop infrastructure as well.) In contrast, if you are writing code that calls a virtual method, it is your job to decide how overriders of the virtual let you know of their existence. There are many ways of accomplishing this.
The second major difference is the impact on your derivation lineage. If the calling code is calling a virtual or interface method, then that forces your called code to derive from the defining base type. This has several side-effects. One is that a single type-definition cannot define two different handling methods for the same notification, a limitation that events don't share (because events are delegate based, and therefore enjoy method-level granularity). Another is that the type, in the case of a virtual in a class, no longer can decide what class to derive from. And a third is that, in the case of an interface, the class has to implement all of the methods in the interface.
I am not arguing against the use of interfaces or virtual methods, by any stretch. I do want to point out, though, how much more heavyweight your dependency relationship is, when the callback mechanism is a virtual method. (That being said, both Java and C++ do, without the benefit of events, similar things to what .NET code does, by using virtuals and interfaces.)
The lines between event/delegate use and virtual/interface use are not always clear-cut; there can be art to this design decision. As evidence to this, notice that List in the Whidbey class library has a Sort overload which takes an IComparer interface reference, and a Sort overload which takes a Comparer delegate reference. Here they provided both delegate and virtual-binding versions, though I think it is safe to say that the newer delegate-based version is likely to be the more convenient to use in many cases.
Alright, so the summary here is that events are another OO mechanism which can reverse, loosen, lessen, or sometimes entirely remove binding dependencies between code. A feature that can be very useful in achieving reusability and extensibility when exploited properly.
I will part on some Whidbey-isms. Whidbey makes event-defining less tedious, by introducing the generic EventHandler delegate type. The introduction of the EventHandler delegate not only reduces the number of delegate types you need, but also enables the following generic event-raiser helper method, which you can package into your classes which raise events:
void RaiseEventHelper<TArgs>(
EventHandler<TArgs> del,
TArgs args)
where TArgs : EventArgs {
if (del != null)
del(this, args);
}
Using a method like this accomplishes the two important tasks of checking the event for null before calling, and making a local-variable-copy (in this case argument-copy) of the delegate before testing and calling.
Alright, I had better get back to work. Cheers!