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).
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).
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: }
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).
1: public class Person
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: }
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: }
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).
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!)
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>>();
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);
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: }