(I originally posted this on my MSDN blog.)
GenesisEngine is still a pretty small code base at this point but there are some design elements that I’m pretty happy with. I’ll run through a series of posts describing these parts so that people can learn from them or maybe critique them and find ways to make them even better.
Event Aggregator
I lifted the design of the event aggregator directly from Jeremy Miller’s posts on the subject and from his implementation in StoryTeller. There’s not much I can add to what Jeremy’s already said but I’ll summarize the concept.
Here’s the very small interface for my aggregator:
public interface IEventAggregator { void SendMessage(T message); void AddListener(object listener); }
An event, or message, can be any type you want to use. You can put as much context data as you wish into your message object. When you send a message, the message object is forwarded to all listeners who have stated a desire to receive that type of message.
When you add listeners to the event aggregator you don’t explicitly list what messages you want the listener to receive. Instead, the listener’s class definition is marked up by implementing one or more flavors of IListener, like so:
public interface IListener { void Handle(T message); } public class Settings : ISettings, INotifyPropertyChanged, IListener, IListener, IListener, IListener, IListener { // Class stuff here }
The event aggregator looks for those interfaces to figure out which messages the listener is interested in:
public void SendMessage(T message) { IEnumerable recipients; lock (_lockObject) { recipients = FindEligibleListeners(); } SendMessageToRecipients(message, recipients); } private IEnumerable FindEligibleListeners() { var eligibleListeners = new List(); foreach (var weakReference in _listeners) { // We need to create a strong reference before testing aliveness // so that the GC doesn't yank it out from under us. Don't convert // this to a LINQ expression because it doesn't guarentee that behavior var strongReference = weakReference.Target as IListener; if (strongReference != null) { eligibleListeners.Add(strongReference); } } return eligibleListeners; }
I use StructureMap as my IoC container and I’m using Jeremy’s neat trick of auto-registering listeners when they are created by the container, so most of the time I don’t even have to explicitly add listeners to the aggregator:
public class EventAggregatorTypeInterceptor : TypeInterceptor { public object Process(object target, IContext context) { context.GetInstance().AddListener(target); return target; } public bool MatchesType(Type type) { return type.ImplementsInterfaceTemplate(typeof(IListener)); } }
I don’t have a RemoveListener() method on my event aggregator right now for two reasons:
- The event aggregator holds weak references to all listeners so it’s not necessary to explicitly remove them for garbage-collection purposes.
- So far I haven’t had a need to remove a listener before the end of its lifetime so there’s been no need to implement that functionality yet.
I’m very happy with this eventing design. It’s simple, has low overhead, and just feels elegant to me.
The source for GenesisEngine is available here.
One thought on “GenesisEngine: The Event Aggregator”