Event aggregation

By Mirek on (tags: event aggregator, categories: code)

In this post I will try to present you my implementation of event aggregator, which I used in WPF MvvM application. I will also try to point out the advantages of my implementation over other found in the internet.

The basic principle of event aggregator is to decouple objects from themselves, but at the same time make them still able to communicate to each other. The idea behind is that the event aggregator allows some objects to register as listeners for specific event type and some other objects to publish events of the same type. Someone could say: “This is just moving dependencies from objects itself to some central point, but it does not decouple themselves totally”.
Well this is partly true. Lets assume we would like to leave the events and communication between objects directly, but at the same time would want to decouple each from the specific implementation of other. Then we would need to define an interfaces for each object, containing signatures of all events it can handle. Then instead of referencing the object in other object we would reference the interface with events and resolve the appropriate object with use of IoC container. Ok, but then we would have as many interfaces as many different objects listening to its event and would need to handle all this stuff manually.
The event aggregator simplifies this in the way that it registers all events from all objects. When the event is then raised event aggregator tries to match and inform all listeners that previously registered for this event. At the end each object is only depended on small IEventAggregator interface

public interface IEventAggregator
{
    void Raise<T>(T message) where T : IEvent;
    void Raise<T>() where T : IEvent, new();
    void RegisterListener<T>(IListenTo<T> listener) where T : IEvent;
    void Unregister(IListenTo listener);
}

and can communicate with all dependent objects not knowing anything about their implementation and even existence.
Nice example of event aggregator was presented by Jeremy Miller, but it has one disadvantage which I think could be improved. Anyway I based my implementation on this a little bit.

 

Let’s see my implementation.

The IListenTo interface define only one method Handle which is pretty self explanatory

public interface IListenTo
{
}
 
public interface IListenTo<T> : IListenTo where T : IEvent
{
    void Handle(T message);
}

For each event, the listener want to listen to, it has to implement separate Handle method with this specific event type as a parameter.

Event aggregator holds the references to all listeners depending on the event type they listen to.

   1: public class EventAggregator : IEventAggregator
   2: {
   3:     private readonly IDictionary<Type, IList<IListenTo>> _listeners = new Dictionary<Type, IList<IListenTo>>();
   4:  
   5:     private readonly object _syncLock = new object();
   6:  
   7:     public EventAggregator()
   8:     {
   9:     }
  10:  
  11:     public void Raise<T>(T message) where T : IEvent
  12:     {
  13:         IEnumerable<IListenTo<T>> callList = null;
  14:         lock (_syncLock)
  15:         {
  16:             Type eventType = message.GetType();
  17:             if (_listeners.ContainsKey(eventType))
  18:                 callList = _listeners[eventType].Cast<IListenTo<T>>();
  19:         }
  20:  
  21:         //handle messages outside above foreach loop
  22:         //since it can cause an indirect change on _listeners list
  23:         if (callList != null)
  24:             foreach (var listener in callList) listener.Handle(message);
  25:     }
  26:  
  27:     public void Raise<T>() where T : IEvent, new()
  28:     {
  29:         Raise(new T());
  30:     }
  31:  
  32:     private void RegisterListener(Type eventType, IListenTo listener)
  33:     {
  34:         lock (_syncLock)
  35:         {
  36:             if (_listeners.ContainsKey(eventType))
  37:             {
  38:                 if (!_listeners[eventType].Contains(listener)) 
  39:                     _listeners[eventType].Add(listener);
  40:             }
  41:             else
  42:                 _listeners.Add(eventType, new List<IListenTo> { listener });
  43:         }
  44:     }
  45:  
  46:     public void RegisterListener<T>(IListenTo<T> listener) where T : IEvent
  47:     {
  48:         Type eventType = typeof(T);
  49:         RegisterListener(eventType, listener);
  50:     }
  51:  
  52:     public void Unregister(IListenTo listener)
  53:     {
  54:         lock (_syncLock)
  55:         {
  56:             foreach (IList list in _listeners.Values) list.Remove(listener);
  57:         }
  58:     }
  59:  
  60: }

In 3rd line we hold the dictionary of all event types with corresponding listeners that can handle those events. Now when the event is raised we have the list of all listeners we need to inform ready under concrete cell of _listeners dictionary. If you analyzed the Jeremy Miller’s implementation, you probably noticed that he holds just a list of all registered listeners and when event is raised he goes through this list and checks which listener can handle the event.


Let’s see the example of usage our event aggregator. The event we want to raise must inherit from IEvent

public class MainNavigation : IEvent
{
    public object Parameter { get; set; }
 
    public MainNavigation(object parameter = null)
    {
        Parameter = parameter;
    }
}

Then the class that want to handle it must register to event aggregator and inherit from IListenTo<MainNavigation>

public class MainViewModel : IListenTo<MainNavigation>
{
    public MainViewModel(IEventAggregator aggregator)
    {
        aggregator.RegisterListener(this);
    }
 
    public void Handle(MainNavigation message)
    {
        //handle event here
    }
}

Having that in place we can raise the MainNavigation event from any place in our application

aggregator.Raise<MainNavigation>(new MainNavigation(navigateParameter));

 

Happy New Year!