Gilbert Corrales and I metup last week here at Redmond and he and I  were talking to about how I wanted to make use of an EventDispatcher approach to routing events around the code base (the analogy we came  up with was "the difference between a cough and a sneeze" - well maybe not a sneeze heh).

EventDispatcher then lead to a framework and before you knew it, we were up to around 2am in my office coding and this is what we ended up with (first cut).

Behold, thine EventDispatcher and all it's glory!

Assume for a second that you have a room full of blind people with no sound, and in the middle you essentially have a machine that handles notifications around Sneezing and Coughing (or any other bodily functions you can think of).

Let's also say that PersonA wants to know if anyone coughs (so he/she react) or that PersonB wants to know if anyone Sneezes.

First things first, lets take a look at the "Room" itself. As it will be the host in this equation (assume it's a really smart room that can detect BodilyFunctions as they happen).

The Room

   1:          private void TheRoom()
   2:          {
   3:              // Add People to the Dark Room.
   4:              Person personA = new Person();
   5:              Person personB = new Person();
   6:              Person personC = new Person();
   7:   
   8:              // Define the curiousity of all Persons..
   9:              personA.Name = "Scott";
  10:              personA.DefineCuriousity("Sneeze");
  11:              personB.Name = "Gilbert";
  12:              personB.DefineCuriousity("Sneeze");            
  13:              personC.Name = "David";
  14:              personC.DefineCuriousity("Cough");
  15:   
  16:   
  17:              // If someone Sneezes/Coughs, let's tell everyone in the room
  18:              // about it via a DisplayBoard (in this case, 3 text fields).
  19:              NexusEvent BodilyFunctionEvent = new NexusEvent(OnBodilyFunction);
  20:              EventDispatcher.Subscribe("Sneeze", BodilyFunctionEvent);
  21:              EventDispatcher.Subscribe("Cough", BodilyFunctionEvent);
  22:   
  23:              // Ok, for arguments sake, lets force a bodily function to occur.
  24:              personA.Sneeze();
  25:              personB.Cough();
  26:              personC.Sneeze();            
  27:   
  28:          }

The Room Dissection

  • We first (Lines 4-6) establish that there are in fact People in the room. This is basically a generic class called "Person" which I'll dissect below (don't worry at this point).
  • We secondly (Lines 9-14) determine whom these people are (names) and what they are curious to know (Sneeze or Cough).
  • We thirdly need to make The Room aware that there is in fact people in the room (Lines 19-20) now and it now needs to listen for a Sneeze or Cough. Once that happens it will then embarrass them all by putting updating a Sign in the background outlining what just happened.

Pretty self-explanatory right?

Let's now take a look at the Anatomy of a person and let's see what makes them tick (don't worry, if your squeamish or can't stand the sight of blood, that's ok, this is rated PG 13 and you won't be offended).

The Person (with their innards exposed).

   1:      public class Person
   2:      {
   3:          public string Name { get; set; }
   4:          private string curiousity { get; set; }
   5:          
   6:          public void DefineCuriousity(string eventType) {
   7:              // You can add your own Subscription Logic here
   8:              // to each individual Person to react to a case of 
   9:              // either a sneeze or cough (ie Move Person 20px to the right
  10:              // then let out a speech bubble "eeww!!!"
  11:          }
  12:   
  13:          public void Sneeze()
  14:          {
  15:              this.PerformBodilyFunction("Sneeze");
  16:          }
  17:   
  18:          public void Cough()
  19:          {
  20:              this.PerformBodilyFunction("Cough");
  21:          }
  22:   
  23:          private void PerformBodilyFunction(string eventType)
  24:          {
  25:              PersonEventArgs prsn = new PersonEventArgs();
  26:              prsn.PersonsBodilyFunction = eventType;
  27:              prsn.PersonsName = this.Name;
  28:              EventDispatcher.Dispatch(eventType, prsn);
  29:          }
  30:      }

The Person Dissection

  • We give the person some basic metadata to help us humanize them.

    Tip:
    For those of you whom are new to .NET you will notice that the property Name only has a get; and set; but nothing else? well that's the power of VisualStudio working there. As when you use the "prop" approach to setters/getters it automates the battle for you, so no more creating set/get values with reference to hidden private properties).

  • image These people are easily influenced via our Microsoft Jedi Mind Trick, so we can define their curiosity for them and such they will react to our preference via the DefineCuriousity() method. Once we tell them "You are curious about a Sneeze" they will in turn agree, ask the EventDispatcher to sign them up for anyone whom sneezes (or in technical terms, any "Sneeze" event that may occur in the application). In the above example though, I've not bothered to add that logic - but you hopefully get the idea.
  • Each person has the ability to also have the Sneeze or Cough knocked out of them, as they are really fragile people (heh). The way in which we do that, again using the Microsoft Jedi Mind Trick is to tell them "You suddenly have an overwhelming need to Sneeze.." and such the Sneeze() method gets fired (via The Room in this example) and as such they tell the EventDispatcher ("I just sneezed, do what you will with that bit of information");
  • Last but again, is the very act of PerformingBodilyFunction, in this case the Person will setup a data packet to describe whom they are and what they just did (PersonEventArgs which inherits NexusEventArgs, a class that we made - see below). They then tell EventDispatcher that the event just occurred (Cough or Sneeze) and passes the EventDispatcher some metadata to help anyone whom needs to know whom they are

    Tip:
    I prefer to keep string pointers as once you start embedding object references into various data packets that float around the code, sometimes you can get lost in a Garbage Collection nightmare. Play it safe, agree that its your job as a developer to keep public objects identifiable as much as you can and unique, so others can find you!)

The EventDispatcher

This concept is something I've used for many years in other languages, and it's quite a nice tool for your day to day RIA solutions. As depending on how you implement it, it can at times get you out of a bind fast and it also does a really nice job of enforcing a layer of abstraction where at times it doesn't appear to be required (yet later saves your bacon, as the age old "ooh glad I had that there now i think about it" does apply with this ball of code)

   1:     /// <summary>
   2:      /// Event delegate definition for Nexus related events.
   3:      /// </summary>
   4:      /// <param name="args">Arguments associated to the event.</param>
   5:      public delegate void NexusEvent(NexusEventArgs args);
   6:   
   7:      /// <summary>
   8:      /// Implementation of a multi-broadcaster event dispatcher.
   9:      /// </summary>
  10:      public class EventDispatcher
  11:      {
  12:          /// <summary>
  13:          /// Holds a list of event handlers subscribed per event.
  14:          /// </summary>
  15:          private static Dictionary<string, List<NexusEvent>> _subscribers = new Dictionary<string, List<NexusEvent>>();
  16:   
  17:          /// <summary>
  18:          /// Subscribes a handler to an event for deferred execution.
  19:          /// </summary>
  20:          /// <param name="evtName">Name of the event to which the handler will be subscribed to.</param>
  21:          /// <param name="eHandler">Handler to be executed everytime the event gets dispatched</param>
  22:          public static void Subscribe(string evtName, NexusEvent eHandler)
  23:          {
  24:              List<NexusEvent> handlers;
  25:              
  26:              if (!_subscribers.TryGetValue(evtName, out handlers))
  27:              {
  28:                  handlers = new List<NexusEvent>();                
  29:                  _subscribers.Add(evtName, handlers);
  30:              }
  31:   
  32:              handlers.Add(eHandler);
  33:          }
  34:   
  35:          /// <summary>
  36:          /// Removes a command from an event for deferred execution.
  37:          /// </summary>
  38:          /// <param name="evtName">Name of the event to which the handler will unsubscribed from.</param>
  39:          /// <param name="eHandler">Handler to be removed from been dispatched.</param>
  40:          public static void RemoveSubscription(string evtName, NexusEvent eHandler)
  41:          {
  42:              List<NexusEvent> handlers;
  43:              
  44:              if (_subscribers.TryGetValue(evtName, out handlers))
  45:              {
  46:                  handlers.Remove(eHandler);
  47:              }
  48:          }
  49:   
  50:          /// <summary>
  51:          /// Broadcasts an event thru its correspondant subscribers.
  52:          /// </summary>
  53:          /// <param name="evtName">Name of the event to be broadcasted.</param>
  54:          /// <param name="args">Arguments associated to the event been propagated.</param>
  55:          public static void Dispatch(string evtName, NexusEventArgs args)
  56:          {
  57:              List<NexusEvent> handlers;
  58:              
  59:              if (_subscribers.TryGetValue(evtName, out handlers))
  60:              {
  61:                  args.EventName = evtName;
  62:   
  63:                  foreach (NexusEvent d in handlers)
  64:                  {
  65:                      d(args);
  66:                  }
  67:              }
  68:          }
  69:      }

The EventDispatcher Dissection.

  • NexusEventArgs is simply a class file that has two properties within itself. First being Data (Dictionary) and Second being EventName (ie "what was my triggers name?".
  • Subscribe & RemoveSubscription are pretty much as you see it. They essentially register an eventName against an object and then keeps such registration in memory until you remove them. It's important to note here, that you really want to think about how you implement this class as memory hoarding can occur fast if you mistreat this class.
  • Dispatch. As seen earlier in the code, this method will basically take a proposed eventName, cycle through it's internal dictionary, locate the appropriate objects which it needs to then notify of such event and then simply do so.

Cautionary Disclaimer and Words of Wisdom.

  • Since EventDispatcher uses static approach to keeping itself a live throughout your application, it's important to note that you need to be a good code-citizen. In that you need to think of ways to Subscribe and RemoveSubscription from the EventDispatcher in a manner that can allow the Garbage Collection to safely remove an object.  In that if you try and remove an object that has a subscription to EventDispatcher, it will only remove the reference to the said object, but the object will continue to stay a live as it still resides inside the EventDispatcher. Consider looking into more how the "IDisposable" concept works (something I'm working on now in how to position EventDispatcher so it keeps things clean overall).
  • You could consider the idea of forcing EventDispatcher to live within specific domains, in that say you had a ShoppingCart and a WishList, both share the same concept in terms of approach and execution (whilst same Events?). You could have a ShoppingEventDispatcher and a WishListEventDispatcher if you wanted to, which basically lets you use the same code base but with different domains to be housed within. Simply inherit the original EventDispatcher, change the way the events are stored internally so that they are stored based on "Domain" and all is forgiven. That and consider using a FrontController.Dispatch() approach to dispatching events vs an EventDispatcher.Dispatch() which keeps the two housed within (ie also consider keeping the FrontController static, whilst the two individual EventDispatchers as being instance based. That way if you decide to nuke the FrontController the two EventDispatchers will die with it).