.NET Matters

Event Accessors

Stephen Toub

Q C# makes it very easy to create events on classes, simply by adding the keyword "event" to a delegate member variable declaration. However, it also allows a property-like syntax where add and remove accessors for the event can be implemented explicitly. Why would I ever want to do that? Wouldn't I always just be recreating the same code the C# compiler generates for me?

Q C# makes it very easy to create events on classes, simply by adding the keyword "event" to a delegate member variable declaration. However, it also allows a property-like syntax where add and remove accessors for the event can be implemented explicitly. Why would I ever want to do that? Wouldn't I always just be recreating the same code the C# compiler generates for me?

A There are several reasons you might want or need to implement your own add and remove accessors for an event in C#. I'll take a look at several of these (this isn't an exhaustive list) and show how custom accessors can enable new functionality or even improve performance.

A There are several reasons you might want or need to implement your own add and remove accessors for an event in C#. I'll take a look at several of these (this isn't an exhaustive list) and show how custom accessors can enable new functionality or even improve performance.

To begin, consider a simple class, MyClass, with a typical instance event, MyEvent:

class MyClass
{
    public event EventHandler MyEvent;
    ...
}

When the C# compiler generates code for MyClass, the output Microsoft® Intermediate Language (MSIL) is identical in behavior to what would have been produced using code like that in Figure 1.

Figure 1 Expanded Event Implementation

class MyClass
{
    private EventHandler _myEvent;

    public event EventHandler MyEvent
    {
        [MethodImpl(MethodImplOptions.Synchronized)]
        add 
        { 
            _myEvent = (EventHandler)Delegate.Combine(_myEvent, value);
        }
        [MethodImpl(MethodImplOptions.Synchronized)]
        remove 
        { 
            _myEvent = (EventHandler)Delegate.Remove(_myEvent, value); 
        }
    }
    ...
}

With the simple event syntax, when you reference MyEvent in your code in order to invoke it, the C# compiler translates that into a call to the underlying delegate, such that a call like the following

MyEvent(this, EventArgs.Empty);

actually compiles down to code like:

_myEvent(this, EventArgs.Empty);

Using the explicit implementation, where you write your own custom add and remove accessors, the compiler doesn't know about the underlying data store for the event, so you can no longer invoke the event using the name of the event itself, but rather you must reference the delegate directly.

This hints at one of the primary reasons for writing your own add and remove accessors: to provide your own underlying data store. One reason you might want to do this is if you have lots of exposed events on your class, but in such a way that only a few are typically in use on an instance at any point in time. In such a scenario, there can be significant memory overhead associated with maintaining a delegate field for each event. Take the Windows® Forms Control class as an example.

In the Microsoft .NET Framework 2.0, Control exposes 69 public events. If each of these events had an underlying delegate field, on a 32-bit system these events would add an overhead of 276 bytes per instance. Instead, Control (or, more specifically, its base class System.ComponentModel.Component) maintains a list of key/value pairs, where the value is a delegate. Every event on Control then has custom add and remove accessors that store the registered delegates for all events into this list, an instance of the System.ComponentModel.EventHandlersList class. Assuming that only a few events on a particular Control are handled, the performance impact of having to search through the EventHandlersList for a particular delegate is minimal, and the memory overhead is limited to what's necessary for the EventHandlersList implementation. You can implement this same pattern in your own classes, as shown in Figure 2.

Figure 2 Custom Store for Event Delegates

class MyClass
{
    private static readonly _myEvent = new object();

    private EventHandlerList _handlers = new EventHandlerList();

    public event EventHandler MyEvent
    {
        add { _handlers.AddHandler(_myEvent, value); }
        remove { _handlers.RemoveHandler(_myEvent, value); }
    }

    private void OnMyEvent()
    {
        EventHandler myEvent = _handlers[_myEvent] as EventHandler;
        if (myEvent != null) myEvent(this, EventArgs.Empty);
    }

    ...
}

I've followed the convention used in Windows Forms, which is to have one static object for each event to use as the key into each EventHandlerList. Since this is a static field, there's only one for the whole AppDomain, rather than one for each instance. You could opt to use a string for the key, but then you run the risk of typos that won't be caught until the relevant lookups fail at runtime; the static object approach moves most of these kinds of failures to compile time.

A related use for explicit event implementation, even if you want to use a single delegate as the backing store for the event, is to augment the delegate with attributes. The most common example of why this may be important is serialization. The .NET serialization mechanism, by default, serializes out all fields of a serializable class, regardless of their accessibility (this is in contrast to XML serialization, where by default only public fields and public properties are serialized). All fields of a serializable class must either themselves be serializable or must be exempted from the serialization process using the NonSerializable attribute (alternatively, the class can implement the ISerializable interface to completely control what data is serialized and how), since they will be traversed by a formatter saving out the object graph.

The trouble with events when it comes to .NET serialization is that they're backed by a private delegate field, and delegates are serializable. When you create a delegate for an instance method, the delegate maintains a reference to the instance on which to call that method, so when you serialize an object that contains an event, the formatter, while walking the object graph, will continue on down through the delegate attempting to serialize any instances registered with that delegate. If you're not aware of this and don't plan accordingly, you could experience some unwelcome side effects. If it's not the behavior you want, a solution is to mark the backing store for the event with the NonSerializableAttribute, as shown in Figure 3. This will cause the formatter to skip that field when serializing out the object graph.

Figure 3 Expanded Event Implementation

[Serializable]
class MyClass
{
    [NonSerializable]
    private EventHandler _myEvent;

    public event EventHandler MyEvent
    {
        [MethodImpl(MethodImplOptions.Synchronized)]
        add 
        { 
            _myEvent = (EventHandler)Delegate.Combine(_myEvent, value);
        }
        [MethodImpl(MethodImplOptions.Synchronized)]
        remove 
        { 
            _myEvent = (EventHandler)Delegate.Remove(_myEvent, value); 
        }
    }
    ...
}

Another use for explicit event implementation is to provide a custom synchronization mechanism (or to remove one). You'll notice in Figure 1 that both the add and remove accessors are adorned with a MethodImplAttribute that specifies that the accessors should be synchronized. For instance events, this attribute is equivalent to wrapping the contents of each accessor with a lock on the current instance:

add { lock(this) _myEvent += value; }
remove { lock(this) _myEvent -= value; }

If you're writing a class that will not be used from multiple threads, there's no point in the overhead of taking this lock, so you could provide a custom implementation that lacks the MethodImplAttribute. However, the overhead of taking such a lock in typical event-usage scenarios is negligible, so it's rare that this would justify a custom implementation. More importantly, however, it's considered poor form to use lock(this), as in effect you're exposing what should be an implementation detail (the lock) to all consumers of your class (they, too, can lock on your instance). If locking is necessary, it's considered a better practice to lock on a private object. And if you're not synchronizing around the "this" reference, then the default event implementation that does synchronize around that reference will most likely be incorrect, since it won't be synchronized in the same manner as the rest of your class.

Another situation exists in which you may need to explicitly implement an event. Consider two interfaces that both have an event with the same name:

interface I1
{
    event EventHandler MyEvent;
}

interface I2
{
    event EventHandler MyEvent;
}

If I want a class to implement both of these interfaces, I can do so as follows:

class MyClass : I1, I2
{
    public event EventHandler MyEvent;
}

Here, MyClass.MyEvent implements both I1.MyEvent and I2.MyEvent. But what if I want MyClass to provide separate implementations for I1.MyEvent and I2.MyEvent? The only way to do so is to use an explicit interface implementation for at least one of the interfaces (though I could do so for both):

class MyClass : I1, I2
{
    public event EventHandler MyEvent;

    private EventHandler _i2MyEvent;
    event EventHandler I2.MyEvent
    {
        add { _i2MyEvent += value; }
        remove { _i2MyEvent -= value; }
    }
}

A fourth reason you might implement custom add or remove accessors is to execute custom logic any time a delegate is registered or unregistered from the event. In what scenarios would that be useful? Several.

Consider the Microsoft.Win32.SystemEvents class. This class exposes over a dozen public static events that are raised in response to various happenings on the system: DisplaySettingsChanged, InstalledFontsChanged, UserPreferenceChanging, and so forth. An application is informed of most of these system notifications through Windows messages. For SystemEvents to raise .NET events for the notifications, it needs a window to listen for them and a thread running a message loop to respond to them. There's overhead involved in such functionality, so SystemEvents only wants to initialize (creating the broadcast window, running the message loop, and so forth) if a delegate is registered with one of the events. And the perfect place to kick off the logic to ensure the class has been initialized is in the add accessor for the delegate, as shown in Figure 4.

Figure 4 Initialization Logic in Add Accessor

class MyClass
{
    private static object _lock = new object();
    private static bool _initialized;
    private static EventHandler _myEvent;

    public static event EventHandler MyEvent
    {
        add
        {
            lock(_lock)
            {
                EnsureInitialized();
                _myEvent += value;
            }
        }
        remove { lock(_lock) _myEvent -= value; }
    }

    private static void EnsureInitialized()
    {
        if (!_initialized)
        {
            ... // do initialization here
            _initialized = true;
        }
    }

    ...
}

Another kind of custom logic you might want executed when a delegate is registered is permission demands. Take the System.Diagnostics.EventLog class as an example. Many of the operations on EventLog are gated by the EventLogPermission, which allows for permission states as specified through an EventLogPermissionAccess enumeration, with members like Administer, Write, and None. The EventLog class exposes a public EntryWritten event that is raised whenever an entry is written to the log, but that act of responding to new entries in an event log requires the Administer permission. Where should that permission be demanded? Code that doesn't have the permission shouldn't be allowed to register a delegate with the event, so a demand can be done in the add accessor, as shown in Figure 5.

Figure 5 Permission Demand in Add Accessor

class MyClass
{
    private EventHandler _myEvent;

    public event EventHandler MyEvent
    {
        add
        {
            new EventLogPermission(
                EventLogPermissionAccess.Administer, ".").Demand();
            _myEvent += value;
        }
        remove { _myEvent -= value; }
    }
}

The System.Console class makes use of custom logic for both custom initialization and permission demands. Console exposes the static CancelKeyPress event, which is raised when the Ctrl+C or Ctrl+Break key combinations are used in the console. Typically these will terminate the process, but if a ConsoleCancelEventArgs delegate is registered with the event, that delegate can override the termination through the ConsoleCancelEventArgs.Cancel property. In order to perform this interception, Console must register a handler to hook the signals using SetConsoleCtrlHandler from Kernel32.dll, and an appropriate place to do this is in the add accessor for the event (the remove accessor contains the corresponding logic to unregister the handler). Additionally, CancelKeyPress requires UIPermissionWindow.SafeTopLevelWindows permission, and thus it's demanded in both accessors for the event.

A much more obscure use of custom logic in an add accessor (for the very advanced developers out there) is useful for events to be raised within constrained execution regions, or CERs. (For a detailed look at CERs, see my article in the October 2005 issue of MSDN®Magazine. The basic idea here is that asynchronous exceptions resulting from situations such as out-of-memory can destabilize an application by interrupting normal code flow in a way that prevents standard backout code from executing correctly. In order to help prevent these types of conditions from occurring, code can be JIT-compiled and prepared ahead of time such that these sorts of conditions are hopefully moved to either before or after the code runs.

Now consider an event that is to be raised from within a CER, where you want to avoid all of these dangerous conditions. You want to prepare ahead of time all of the code to be executed in the CER, but if you're raising an event to which any delegate can be registered, unprepared code could in fact run within the CER. This is exactly the situation that several events on the AppDomain class run into. For example, AppDomain.ProcessExit is raised from within a CER, so all delegates registered with it should be prepared ahead of time. How can this be accomplished? By adding code to the event's add accessor that prepares the supplied delegate before it's registered with the event, as shown in Figure 6.

Figure 6 Delegate Preparation in Add Accessor

class MyClass
{
    private EventHandler _myEvent;

    public event EventHandler MyEvent
    {
        add
        {
            System.Runtime.CompilerServices.
                RuntimeHelpers.PrepareDelegate(value);
            _myEvent += value;
        }
        remove { _myEvent -= value; }
    }
}

This is just a subset of reasons why you might want to provide your own add and remove accessors for an event. Most of the time, the stock accessors generated by the compiler are fine. But there may be times when you need more control, making this a very handy feature to have in your back pocket.

Send your questions and comments for Stephen to netqa@microsoft.com.

Stephen Toub is the Technical Editor for MSDN Magazine.