In my last
post, I covered how you can implement a publish/subscribe system for sending events
between components of your Windows Forms application. Using this model, it is easy
to create separate components that don’t rely on each other’s implementation details
in order to provide a consistent experience for the user. A button on the toolbar
can be disabled by an action on the data entry screen…without hard references between
the two.
However, the implementation from last time required a new aggregator and set of interfaces
for 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 IListener
interface 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 message
type they can handle. (This is great because one class can listen for multiple types
of messages!)
Event Aggregator Interface
Similarly, the Event Aggregator needs a generics reset. Notice that the Add and Remove
methods 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 Members
public void SendMessage<T>(T
message)
{
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 IEnumerable
enumerable, 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 language
skills.
With the code we have so far, we can easily create a new message in the system without
modifying the aggregator code. I like adding the methods as child classes of their
receivers (when they are receiver-specific).
There is, however, still a problem. In the type of application that needs this eventing
system, you are likely going to need to do some background threading to keep the UI
responsive. The Event Aggregator we have doesn’t do anything to keep itself or the
rest of the application synchronized.
What happens when a background thread sends a message that the receiver needs to act
on 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 upgrade
your EventAggregator to be the thread master!