Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

An Event Pool

0.00/5 (No votes)
21 Feb 2004 1  
An event pool helps manage large amounts of events that otherwise clutter up your code and make maintenance difficult.

For a .NET 2.0 version, see Pete O'Hanlon's article.

Preface

The EventPool is part of the Model-View-Controller (MVC) technology roadmap series, which includes:

  • EventPool (this article)
  • IMemento (tbd)
  • UndoBuffer (tbd)
  • StateManager (tbd)
  • XmlGuiGenerator
  • Form2Xml (tbd)

Introduction

Event pools are useful for managing large sets of events that you may typically encounter when working with an MVC model. A rich model often often contains many properties and undergoes many different state changes. Declaring a unique event (and sometimes a delegate with a subclassed EventArgs parameter) is time consuming and adds to the maintenance of the model.

The drawback to this approach is that subclassed EventArgs, while supported, must be cast to the correct subclass in the event handler. This moves the detection of incorrectly constructed event handlers from compile time to run time.

The Problem

Take a simple model, such as a four register stack used by a simple RPN calculator:

protected string[] registerStack={"0.00", "0.00", "0.00", "0.00"};

What we'd like to do is to provide an event that is fired whenever one of the register values changes so that any associated views can update the user interface, ex:

Let's say that the model represents the events with four properties:

  • X
  • Y
  • R3
  • R4

Typically, we would construct a delegate for the event signature:

public delegate void RegisterChangeDelegate(object sender, EventArgs args);

and our four events:

public event RegisterChangeDelegate RegisterXChangeEvent;
public event RegisterChangeDelegate RegisterYChangeEvent;
public event RegisterChangeDelegate RegisterR3ChangeEvent;
public event RegisterChangeDelegate RegisterR4ChangeEvent;

and invoke our events in the property setters. For example, the X register setter:

public string X
{
  get {return registerStack[0];}
  set
  {
    registerStack[0]=value;
    if (RegisterXChangeEvent != null)
    {
      RegisterXChangeEvent(this, EventArgs.Empty);
    }
  }
}

The view informs the model of it's interest in change event using:

  model.RegisterXChangeEvent+=new EventHandler(RegisterXChangeEventHandler);

This will allow our views of the model to wire up event handlers that are invoked when the model changes, and all is happy with world. There are three problems:

  • We've created event definitions that are a maintenance liability (albeit a small one in this example).
  • Unless the programmer is very good, he/she will forget to instrument the event, as I have done here.
  • We have to remember to always check if the event handler has actually been assigned.

The last problem can be worked around with some defensive event publishing, courtesy of Juval Lowy's and Eric Gunnerson's C# Best Practices Power Point presentation, downloadable here:

public class EventsHelper
{
   public static void Fire(Delegate del,params object[] args)
   {
      if(del == null)
      {
         return;
      }
      Delegate[] delegates = del.GetInvocationList();
      foreach(Delegate sink in delegates)
      {
         try
         {
            sink.DynamicInvoke(args);
         }
         catch{} 
      }
   }
}

This eliminates the "if" block and ensures that, even if the event isn't assigned, the setter will still work. For example, this is how the new "X" setter would appear:

public string X
{
  get {return registerStack[0];}
  set
  {
    registerStack[0]=value;
    EventsHelper.Fire(RegisterXChangeEvent);
  }
}

Much cleaner, but it still doesn't address the other two problems--lots of events and lack of instrumentation.

The Solution

The solution is an event pool. The event pool simplifies the management of many different delegates and their associated events and event handlers. Events are abstracted by associating the event handler with a tag. Events are invoked by referring to the tag (rather than the event delegate). The event pool also lends itself well to instrumentation so that the programmer doesn't have to remember to do this.

Given the above example, when we use an event pool, the following code is eliminated:

public delegate void RegisterChangeDelegate(object sender, EventArgs args);
public event RegisterChangeDelegate RegisterXChangeEvent;
public event RegisterChangeDelegate RegisterYChangeEvent;
public event RegisterChangeDelegate RegisterR3ChangeEvent;
public event RegisterChangeDelegate RegisterR4ChangeEvent;

The model fires any assigned events via the tag mechanism. For example:

public string X
{
  get {return registerStack[0];}
  set
  {
    registerStack[0]=value;
    eventPool.FireEvent("X");
  }
}

The view expresses its interest in the model's event in a slightly different manner:

model.EventPool.Subscribe("X",new EventHandler(RegisterXChangeEventHandler));

Loose Coupling

The danger, of course, is that the events to which you can subscribe are now very loosely associated with the events that published. How do you know if you are subscribing to an event that is still being published?

The answer is to tell the event pool what events you are publishing, via the Publish method:

virtual public void Publish(params object[] tags)
{
  foreach(object tag in tags)
  {
    eventTagToDelegateMap.Add(tag, null);
  }
}

The model, for example, publishes the events that it fires:

eventPool.Publish("X", "Y", "R3", "R4", "EntryStateChanged");

Now, when the view subscribes to the model's events, the event pool will verify that the event has been published and emit a warning if not (or you can change the code and have it throw an exception). The Subscribe method is overloaded so that a subscriber can optionally disable this checking, in the event (hahaha) that the publisher hasn't registered the events yet or is doing some dynamic event publication.

The Demonstration

The download includes an RPN calculator as a demonstration for the EventPool class. This demo a very clean MVC model and has a lot of events flying around, which are managed by the EventPool.

Documentation

Documentation for the EventPool can be found on my website. This documentation will be extended to include the other technologies in the MVC model as the additional articles in this series are published.

The only interesting thing about the EventPool is the Subscribe method, which builds a multicast delegate:

virtual public void Subscribe(
     object tag,
     EventHandler eventHandler,
     bool loose)
{
  // do we verify that the handler exists?

  if (!loose)
  {
    if (!eventTagToDelegateMap.Contains(tag))
    {
      Trace.WriteLine("Event Pool does not contain an entry for: "+
                       tag.ToString());
    }
  }

  // create the delegate for this event handler

  MulticastDelegate newDelegate=new MulticastDelegate(eventHandler);

  // if this is a new tag...

  if (!eventTagToDelegateMap.Contains(tag))
  {
    // ...add a new tag to the map.

    eventTagToDelegateMap.Add(tag, newDelegate);
  }
  else
  {
    // ...otherwise, combine the new delegate with the delegates for

    // existing mapping.

    MulticastDelegate dlgt=(MulticastDelegate)eventTagToDelegateMap[tag];

    // if the tag has associated delegates already...

    if (dlgt != null)
    {
      // ... combine the new delegate with the existing ones.

      dlgt=(MulticastDelegate)Delegate.Combine(dlgt, newDelegate);
      // delegates are data structures, which are therefore passed by value.

      eventTagToDelegateMap[tag]=dlgt;
    }
    else
    {
      // ... otherwise just assign the new delegate.

      eventTagToDelegateMap[tag]=newDelegate;
    }
  }
}

Conclusion

I personally find this approach more maintainable and easier to use when working with MVC models that publish many events.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here