Events are the most commonly used form of callbacks (constructs that allow the framework to call into user code). Other callback mechanisms include members taking delegates, virtual members, and interface-based plug-ins. Data from usability studies indicate that the majority of developers are more comfortable using events than they are using the other callback mechanisms. Events are nicely integrated with Visual Studio and many languages.
It is important to note that there are two groups of events: events raised before a state of the system changes, called pre-events, and events raised after a state changes, called post-events. An example of a pre-event would be
Form.Closing, which is raised before a form is closed. An example of a post-event would be
Form.Closed, which is raised after a form is closed.
✔️ DO use the term "raise" for events rather than "fire" or "trigger."
✔️ DO use System.EventHandler<TEventArgs> instead of manually creating new delegates to be used as event handlers.
✔️ CONSIDER using a subclass of EventArgs as the event argument, unless you are absolutely sure the event will never need to carry any data to the event handling method, in which case you can use the
EventArgs type directly.
If you ship an API using
EventArgs directly, you will never be able to add any data to be carried with the event without breaking compatibility. If you use a subclass, even if initially completely empty, you will be able to add properties to the subclass when needed.
✔️ DO use a protected virtual method to raise each event. This is only applicable to nonstatic events on unsealed classes, not to structs, sealed classes, or static events.
The purpose of the method is to provide a way for a derived class to handle the event using an override. Overriding is a more flexible, faster, and more natural way to handle base class events in derived classes. By convention, the name of the method should start with "On" and be followed with the name of the event.
The derived class can choose not to call the base implementation of the method in its override. Be prepared for this by not including any processing in the method that is required for the base class to work correctly.
✔️ DO take one parameter to the protected method that raises an event.
The parameter should be named
e and should be typed as the event argument class.
❌ DO NOT pass null as the sender when raising a nonstatic event.
✔️ DO pass null as the sender when raising a static event.
❌ DO NOT pass null as the event data parameter when raising an event.
You should pass
EventArgs.Empty if you don’t want to pass any data to the event handling method. Developers expect this parameter not to be null.
✔️ CONSIDER raising events that the end user can cancel. This only applies to pre-events.
Use System.ComponentModel.CancelEventArgs or its subclass as the event argument to allow the end user to cancel events.
Custom Event Handler Design
There are cases in which
EventHandler<T> cannot be used, such as when the framework needs to work with earlier versions of the CLR, which did not support Generics. In such cases, you might need to design and develop a custom event handler delegate.
✔️ DO use a return type of void for event handlers.
An event handler can invoke multiple event handling methods, possibly on multiple objects. If event handling methods were allowed to return a value, there would be multiple return values for each event invocation.
✔️ DO use
object as the type of the first parameter of the event handler, and call it
✔️ DO use System.EventArgs or its subclass as the type of the second parameter of the event handler, and call it
❌ DO NOT have more than two parameters on event handlers.
Portions © 2005, 2009 Microsoft Corporation. All rights reserved.
Reprinted by permission of Pearson Education, Inc. from Framework Design Guidelines: Conventions, Idioms, and Patterns for Reusable .NET Libraries, 2nd Edition by Krzysztof Cwalina and Brad Abrams, published Oct 22, 2008 by Addison-Wesley Professional as part of the Microsoft Windows Development Series.