In my lastpost, I covered how you can implement a publish/subscribe system for sending eventsbetween components of your Windows Forms application. Using this model, it is easyto create separate components that don’t rely on each other’s implementation detailsin order to provide a consistent experience for the user. A button on the toolbarcan be disabled by an action on the data entry screen…without hard references betweenthe two.
However, the implementation from last time required a new aggregator and set of interfacesfor each message type that needed to be passed around. Let’s fix that with generics.
Listener Interface
First we replace the IEventReciever interface from last time with a generic IListenerinterface that uses a generic type for the message object.
public interface IListener<T>{void Handle(T message);}
All your subscribers now implement an IListener<Message> for each messagetype they can handle. (This is great because one class can listen for multiple typesof messages!)
Event Aggregator Interface
Similarly, the Event Aggregator needs a generics reset. Notice that the Add and Removemethods now accept any old object.
public interface IEventAggregator{void SendMessage<T>(T message);void AddListener(object listener);void RemoveListener(object listener);}
Event Aggregator
public class EventAggregator: IEventAggregator{private readonly List<object>listeners = new List<object>();#region IEventAggregator Memberspublic void SendMessage<T>(Tmessage){listeners.CallOnEach<IListener<T>>(x => { x.Handle(message); });}public void AddListener(object listener){if (listeners.Contains(listener)) return;listeners.Add(listener);}public void RemoveListener(object listener){listeners.Remove(listener);}#endregion}
Not much new here….except for that CallOnEach method. Where did that come from?
Extension Methods
We need to add a few utility methods to the IEnumerables so that we can send our messages:
public static void CallOnEach<T>(this IEnumerableenumerable, Action<T> action) where T : class{foreach (object o in enumerable){o.CallOn(action);}}public static void CallOn<T>(this object target,Action<T> action) where T : class{var subject = target as T;if (subject != null){action(subject);}}
Put those in a likely static class somewhere.
Are we there yet?
This Event Aggregator is getting powerful. We should kill it before it develops languageskills.
With the code we have so far, we can easily create a new message in the system withoutmodifying the aggregator code. I like adding the methods as child classes of theirreceivers (when they are receiver-specific).
There is, however, still a problem. In the type of application that needs this eventingsystem, you are likely going to need to do some background threading to keep the UIresponsive. The Event Aggregator we have doesn’t do anything to keep itself or therest of the application synchronized.
What happens when a background thread sends a message that the receiver needs to acton by talking to the UI thread? Do you write a bunch of Invoke() code everywhere?
As it turns out, there is a better way. And my next post will show you how to upgradeyour EventAggregator to be the thread master!