WeakEventManager

There is a well known issue for Events in .NET.  The Event's InvocationList maintains a strong reference to any listeners.  This means as long at the source object remains alive, the garbage collector can not collect any listeners.  This is generally a good thing, for obvious reasons, but it means that if your listeners are going to have a shorter life span independent from your source object then you have to remove them from the InvocationList.  Unfortunately, it's not always easy, or even possible, to determine when to do this.  To resolve this issue Microsoft invented the WeakEvent Pattern.

From the MSDN article on the WeakEvent Pattern:

In typical applications, it is possible that handlers that are attached to event sources will not be destroyed in coordination with the listener object that attached the handler to the source. This situation can lead to memory leaks. Windows Presentation Foundation (WPF) introduces a particular design pattern that can be used to address this issue, by providing a dedicated manager class for particular events and implementing an interface on listeners for that event. This design pattern is known as the WeakEvent pattern.

To implement the pattern, your listeners derive from IWeakEventListener and you derive a WeakEventManager for the event which is used to indirectly handle events.  This may sound complicated, but it's really not to bad once you've read the documentation.  However, despite being simple, there's a lot of code you have to write.  From the MSDN on implementing a WeakEventManager:

Notes to Inheritors: Classes that derive from WeakEventManager class should do the following:

  • Provide a static "AddListener" method. Sources call AddListener to add a listener for the managed weak event. Your implementation calls to implement this behavior.

  • Provide a static "RemoveListener" method. Sources call RemoveListener to add a listener for the managed event. Your implementation calls ProtectedRemoveListener to implement this behavior.

  • Override StartListening to cast the source to the type that owns the event, and connect the handler on the source to the event being managed.

  • Override StopListening to cast the source to the type that owns the event, and disconnect the handler on the source to the event being managed.

  • Implement the handler, which should call DeliverEvent, so that the managed event is forwarded to its weak event pattern listeners.

  • Provide a CurrentManager property that returns the specific manager type being implemented. The get accessor for CurrentManager should call GetCurrentManager to make sure that there is not already an initialized instance. If so, that instance is returned, properly typed. If there is no initialized instance, the get accessor should call SetCurrentManager to initialize one.

None of this is rocket science, but that's an awful lot of tedious code just to implement the WeakEventManager.  What if I told you there was a way you could use this pattern with out having to write most of that tedious code for a derived WeakEventManager?

class MyEventManager : WeakEventManagerBase<MyEventManager, IMyEvent>
{
   protected override void StartListeningTo(IMyEvent source)
   {
      source.MyEvent += DeliverEvent;
   }

   protected override void StopListeningTo(IMyEvent source)
   {
      source.MyEvent -= DeliverEvent;
   }
}

The WeakEventManagerBase class is a generic type that implements most of the necessary parts described in the Notes to Inheritors above.  You're only responsibility is then to implement the type safe "StartListeningTo" and "StopListeningTo" methods, as I did above.  Here's the code for the WeakEventManagerBase:

public abstract class WeakEventManagerBase<T, TSource> : WeakEventManager
    where T : WeakEventManagerBase<T, TSource>, new()
    where TSource : class
{
    public static T Current
    {
        get
        {
            Type managerType = typeof(T);
            T manager = WeakEventManager.GetCurrentManager(managerType) as T;
            if (manager == null)
            {
                manager = new T();
                WeakEventManager.SetCurrentManager(managerType, manager);
            }
            return manager;
        }
    }

    public static void AddListener(TSource source, IWeakEventListener listener)
    {
        Current.ProtectedAddListener(source, listener);
    }

    public static void RemoveListener(TSource source, IWeakEventListener listener)
    {
        Current.ProtectedRemoveListener(source, listener);
    }

    protected override sealed void StartListening(object source)
    {
        StartListeningTo(source as TSource);
    }

    protected abstract void StartListeningTo(TSource source);

    protected override sealed void StopListening(object source)
    {
        StopListeningTo(source as TSource);
    }

    protected abstract void StopListeningTo(TSource source);
}

While working on this I discovered some interesting things about the WeakEventManager.  For instance, the framework provides several specific implementations of this class (LostFocusEventManager, DataChangedEventManager, CurrentChangedEventManager, CurrentChangingEventManager, PropertyChangedEventManager and CollectionChangedEventManager).  Not all of these EventManagers follow the exact pattern above.  For instance, the PropertyChangedEventManager has an "AddListener" that takes 3 parameters instead of 2.  The third parameter is the property name that you wish to receive change notifications for.  That was interesting, so I fired up Reflector to look at the implementation.  Turns out there's an indexer (Item) on the WeakEventManager that can be used to store data "on" the source.  I could write a whole article on this topic, but I won't go into it here.  If this interests you, though, I'd highly recommend using Reflector to look at the implementation of a few of these types.  It's educational.  All that said, though, I wonder about the PropertyChangedEventManager.  The implementation seems to be very specifically tied to the needs of data binding, since you register not for any change but for a specific change.  If I were to design this more generically, I'd provide two "AddListener" (and "RemoveListener") methods, one that took the third parameter and one that did not.  This would complicate the implementation, but I think it would better serve clients of the API to have the flexibility here.  If I weren't going to provide both, the logical one to choose would be the non-specific one, IMO.  So, I find this class a little strange.

One thing not addressed yet is how to make implementing your listeners a little easier.  I may post on that later, but the general idea would be to provide a helper to dispatch based on the EventManager type via a Dictionary.  A rough outline of the idea, not yet reusable code, would be something along the lines of this.

class MyListener : IWeakEventListener
{
   Dictionary<Type, EventHandler> dispatchMap;

   void RegisterListener(Type managerType, EventHandler eventHandler)
   {
      dispatchMap[managerType] = eventHandler;
   }

   public bool ReceiveWeakEvent(Type managerType, object sender, EventArgs e)
   {
      EventHandler handler = dispatchMap[managerType];
      handler(sender, e);
   }
}

Note that there's a lot of issues in the above idea.  It's definitely for illustration of an idea only.

Add a Comment