Saturday, February 14, 2009

Preventing Duplicate Subscriptions to Your Events

Occasionally a question will come up regarding how to avoid duplicate subscriptions to specific events within classes that have been declared. The typical answer is to tell the reader that they can simply get in the habit of unsubscribing to the event immediately before resubscribing to the event. This situation would look like the following:


Widget.Event -= new EventHandler(HandleEvent);
Widget.Event += new EventHandler(HandleEvent);

While this typically will work fine, mainly because there is no error thrown when the code calls to unsubscribe an event handler that doesn't exist in the event before the call, calling this unsubscribe repeatedly may be unnecessary, and may not be perfectly feasible, especially considering that assemblies shipped as third party assemblies may be used by developers who aren't familiar with this subtle trick of event handler management.

Thankfully, unbeknownst to many developers out there, you can define the behaviors of your add and remove methods within your event subscription code. In fact, the explicit declaration of an event's code can have several benefits, and is as easy to implement as a standard (non-automatic) property. Some of the added benefits include the ability to control which object is locked during adding and removing methods (the default for instance types is the instance of the class the event resides in, and for static classes, it's the type object), as well as the internal behavior of the subscriptions when the += and -= tokens are used in relationship to events. Handling the internal behavior can help to prevent duplicate subscriptions within a class.

The following is the typical implementation of events within a class:


public class NormalEventClass
{
public event EventHandler Event;

public void OnEvent()
{
EventHandler instance = Event;
if (instance != null)
instance(this, EventArgs.Empty);
}
}

public class Program
{
static void Main(string[] args)
{
Console.WriteLine("Subscribing to normal event twice and raising event.");
NormalEventClass normalClass = new NormalEventClass();
normalClass.Event += HandleEvent;
normalClass.Event += HandleEvent;
normalClass.OnEvent();
Console.ReadLine();
}

static void HandleEvent(object sender, EventArgs e)
{
Console.WriteLine("Event Raised");
}
}

Notice that running this code causes the message "Event Raised" to appear twice on the screen. The reason for this is because the event handler is listed twice in the internal list of delegates that is called when the event is raised. This is where the trick comes in. Remember I said you can unsubscribe before subscribing to prevent duplicate event handlers from appearing in the delegate list? You could simply do this by fleshing out the declaration of your events. Let's say we use the following class instead:


public class SingleSubscriptionEventClass
{
private EventHandler _event;

private object _eventLock = new object();

public event EventHandler Event
{
add
{
lock (_eventLock) { _event -= value; _event += value; }
}
remove
{
lock (_eventLock) { _event -= value; }
}
}

public void OnEvent()
{
EventHandler instance = _event;
if (instance != null)
instance(this, EventArgs.Empty);
}
}

public class Program
{
static void Main(string[] args)
{
Console.WriteLine("Subscribing to single-subscription event twice and raising event.");
SingleSubscriptionEventClass ssClass = new SingleSubscriptionEventClass();
ssClass.Event += HandleEvent;
ssClass.Event += HandleEvent;
ssClass.OnEvent();
Console.ReadLine();
}

static void HandleEvent(object sender, EventArgs e)
{
Console.WriteLine("Event Raised");
}
}

Now you'll notice a couple of things in the code above. First, I've declared an internal delegate of the same type as the event handler. This delegate is the invocation list that will be used when the event is raised. Second, I've declared a single object within the code that is locked when adding and removing event handlers to the event. When declaring the NormalClass, the compiler automatically locks an object when adding and removing the event handlers, but the object it locks is the instance of the object in which the event resides. It's typically better to lock a neutral object for performance reasons. Lastly, I've added the add and remove methods to the event, and in them, you'll notice, I've removed the previous event handler (if it exists) from the invocation list immediately before adding the event handler to the invocation list. Doing this ensures that each object can only subscribe to an event with a specific event handler once, and no more than once. Now, regardless of the number of times the subscription is performed, at most, only one instance of that subscriber's event handler will reside in the invocation list, thus preventing repetitive calls to the subscriber's event handler.