Thursday, June 25, 2009

Event Extensions: Methods for firing events and managing event listeners

In most modern C# applications, generous usage of events have become the norm. Gone are the days where we were afraid to use them because of the "black magic" code that they seemed to execute.

Today, perhaps the opposite is true, and they are used too lightly, with disregard for the memory leeches they are (those small, black  slimly kind that you're never aware of what damage they're doing until it's too late).

Here's some code to make usage of the leeches a little easier :).

The typical firing of an event from C# code looks something like this:

   1: if (MyEvent1 != null)
   2: {
   3:     MyEvent1(this, EventArgs.Empty);
   4: }

There are 2 essential problems with this code:

  1. It's too many lines of code for something so mundane.
  2. What happens if there are more than 1 listeners listening to the event, and one of them throws an exception? Normal .NET behaviour dictates that the other listeners never get to hear about the event, but what if it's important that all listeners know about the event before application execution is halted?

Solution to problem #1: wrap the logic in an extension method

   1: MyEvent1.Raise(this, EventArgs.Empty);

there - isn't that better? Here's sample of the implementation:

   1: public static void Raise(this EventHandler handler, object sender, EventArgs e)
   2: {
   3:     if (handler != null)
   4:     {
   5:         handler(sender, e);
   6:     }
   7: }

Solution to problem #2: expand on the extension method (now that all the logic is conveniently in one place)

   1: MyEvent1.Raise(this, EventArgs.Empty, true);

...where the boolean parameter tells the extension method to make sure that all the event listeners get to hear about the event before throwing an exception. The exception that is then thrown, is an EventListenerException, which is just basically a wrapper for all exceptions that occurred on the event listener's event handling code.

sample implementation: (called from the event extension method)

   1: private static void ManageEventListeners(IEnumerable<Delegate> delegates, object sender, EventArgs e)
   2:       {
   3:           EventListenerException eventListenerException = null;
   4:           foreach (Delegate listner in delegates)
   5:           {
   6:               //the listner could throw an unhandled exception in the handler method
   7:               //make sure that the other listners still handle event if this is the case
   8:               try
   9:               {
  10:                   listner.DynamicInvoke(new[] {sender, e});
  11:               }
  12:               catch (TargetInvocationException ex)
  13:               {
  14:                   //add a new event listner
  15:                   EventListener eventListener = new EventListener
  16:                                                   {
  17:                                                       Listener = listner,
  18:                                                       ListenerException = ex.InnerException
  19:                                                   };
  20:                   
  21:                   //NOTE: The exception which occurs is "TargetInvokationException", but the 
  22:                   //actual exception which occurs in the listner is contained in the innerException
  23:  
  24:                   //add listner to eventListnerException
  25:                   if (eventListenerException == null)
  26:                   {
  27:                       eventListenerException = new EventListenerException();
  28:                   }
  29:                   eventListenerException.EventListenerCol.Add(eventListener);
  30:               }
  31:           }
  32:  
  33:           //if an exception was thrown by one of the listner's event handlers
  34:           if (eventListenerException != null)
  35:           {
  36:               throw eventListenerException;
  37:               //then re-throw the exception that thrown in the handler 
  38:           }
  39:       }

Here's some sample code with unit tests included in case you need to play with the code (NUnit 2.4.7 & VS 2008)



PS: It's Silverlight compatible (of course :) )

No comments: