The blog of dlaa.me

Controls are like diapers: you don't want a leaky one [Implementing the WeakEvent pattern on Silverlight with the WeakEventListener class]

One of the nice things about developing on a platform that uses a garbage collecting memory manager (like Silverlight and WPF) is that the traditional concerns about memory leaks pretty much go away; most common types of memory leaks are impossible in a garbage collected environment. I say "most" and not "all" because there are still a few ways to leak memory - typically by creating a reference to an object and then "forgetting" about that reference. Sometimes this forgetfulness is simply an oversight on the part of the developer, but sometimes it is due to the reference being created in such a way that it's not obvious it even exists...

One of the easiest ways to create a "hidden" reference is by creating an event handler. Greg Schechter blogged in detail about the event handler situation back in 2004, and interested readers would do well to refer to his post for more details and some pretty diagrams. To summarize the issue briefly: when component A attaches an event handler to an event on component B, what happens behind the scenes is that component B creates a reference to component A - which it needs in order to provide a notification to A when the event is fired. This "backwards" reference is a bit subtle, but if you know what you're looking for, it's usually not too hard to spot.

What's more challenging is when a component you're using creates one of these backwards references in response to an action that's (superficially) completely unrelated to the event handling. As an exercise, see if you can spot the event handler here:

control.ItemsSource = collection;

Not so obvious, huh? :) The context needed to understand what's going on here is that an the control variable is some type that derives from ItemsControl (such as ListBox) and the collection variable is some type that derives from ObservableCollection<T>. It so happens that when an ItemsControl sees an assignment to its ItemsSource property of an object that implements INotifyCollectionChanged, it automatically adds a handler for that object's CollectionChanged event. And there's the potentially troublesome backward reference...

All it takes to start leaking memory at this point is an application with a long-lived reference to an ObservableCollection that gets passed to a short-lived ItemsControl that gets discarded. What can end up happening is that all of the references to the ItemsControl go away when it is discarded except for the one the ItemsControl itself created to listen to CollectionChanged events. Unless something is done to avoid this problem, the long-lived reference to the ObservableCollection will inadvertently keep the ItemsControl and all of its references alive considerably longer than the application developer expects. Furthermore, if the application is creating and discarding these ItemsControls on a fairly regular basis, it will quickly build up a sizable memory leak due to the accumulation of all the "discarded" ItemsControls.

As luck would have it, this situation was well understood and by the WPF team and that platform exposes APIs to implement what they call the WeakEvent pattern. WPF makes use of the WeakEvent pattern for its own ItemsControl, so the scenario described above isn't a problem in practice.

However, the WeakEvent pattern APIs don't exist in Silverlight 2 and the scenario above actually is a problem for ItemsControl and its subclasses as well as the DataGrid, and Charting's Series classes (each of which has a non-ItemsControl-based ItemsSource property). The good news is that Silverlight intends to fix this problem for ItemsControl-derived classes as part of a future release. Unfortunately, that doesn't help Charting - and besides I'd like to help customers avoid this problem on the current release...

So I got in touch with Silverlight developer Ivan Naranjo to see if his team had anything we could make use of and he kindly responded with something very much like the WeakEventListener class you see below. (For my part, I just tweaked things to address a few code- and source-analysis warnings and made a small change to avoid a possible area of confusion Ivan and I independently agreed on.) If you read Greg Schechter's post, the technique and implementation should look pretty familiar - WeakEventListener is a small, intermediary class that can be attached to an event so the resulting backwards reference affects only the extremely lightweight WeakEventListener and not the heavyweight class that created it. This avoids the costly "hidden" reference and allows the owning class to be garbage collected as soon as it is no longer in use. It was easy to add a WeakEventListener in Charting's Series.ItemsSource handler - and now our users don't have to worry about inadvertently leaking Chart instances!

To show just how easy it is to use WeakEventListener, I created a sample project that you can download and experiment with as you read the rest of this post. Here's what it looks like:

WeakEventListener Sample Application

The sample application includes two custom controls: LeakyControl (which has a typical ItemsSource implementation) and FixedControl (which uses WeakEventListener). To see the problem and solution in action, start the application, click the "Check Status" button to verify both controls are present, then click "Remove From UI" to discard both controls. If you click "Check Status" again at this point, you'll see that both controls are still present - and that's expected because the garbage collector hasn't needed to run and so nothing has been done to clean up. Now click "Garbage Collect" and then "Check Status" again. You'll see that LeakyControl is still present, but FixedControl is gone. Yay, WeakEventListener works! :)

Now, take things just a bit further and click "Clear ItemsSource", then "Garbage Collect", then "Check Status". We see that LeakyControl is gone as well! Why? Because by setting ItemsSource property of LeakyControl to null, we've explicitly told it we were done with the old collection and it knew enough to remove its event handler from that collection - thereby breaking the backwards reference and making itself eligible for clean-up during the next garbage collection. And, in fact, this is the workaround for controls that don't implement some form of the WeakEvent pattern and suffer from leaky behavior because of it: null-out the relevant property and hope they're kind enough to detach their event handlers in response. Depending on your application scenario, implementing this work around may be quite simple - or it may be almost impossible if your application has no way of knowing when some subcomponent has gotten into this situation - or no explicit knowledge of when affected controls are removed from the user interface. That's why it's nice when controls behave properly on their own - they work no matter what you do! :)

For an idea of how an easy it is to make use of WeakEventListener in a control, here is the relevant (leaky) code from LeakyControl:

// Change handler for the ItemsControl.ItemsSource-like DependencyProperty
private void OnItemsSourceChanged(IEnumerable oldValue, IEnumerable newValue)
{
    // See if the old value implements INotifyCollectionChanged
    var oldNotifyCollectionChanged = oldValue as INotifyCollectionChanged;
    if (null != oldNotifyCollectionChanged)
    {
        // It does; detach from the CollectionChanged event
        oldNotifyCollectionChanged.CollectionChanged -= OnCollectionChanged;
    }
    // See if the new value implements INotifyCollectionChanged
    var newNotifyCollectionChanged = newValue as INotifyCollectionChanged;
    if (null != newNotifyCollectionChanged)
    {
        // It does; attach to the CollectionChanged event
        newNotifyCollectionChanged.CollectionChanged += OnCollectionChanged;
    }
}

And here's what that same code looks like in FixedControl where WeakEventListener is used to avoid the backward reference problem:

// Change handler for the ItemsControl.ItemsSource-like DependencyProperty
private void OnItemsSourceChanged(IEnumerable oldValue, IEnumerable newValue)
{
    // See if the old value implements INotifyCollectionChanged
    var oldNotifyCollectionChanged = oldValue as INotifyCollectionChanged;
    if (null != oldNotifyCollectionChanged)
    {
        // It does; detach our WeakEventListener and clear our reference
        _weakEventListener.Detach();
        _weakEventListener = null;
    }
    // See if the new value implements INotifyCollectionChanged
    var newNotifyCollectionChanged = newValue as INotifyCollectionChanged;
    if (null != newNotifyCollectionChanged)
    {
        // It does; create a WeakEventListener, attach us to it, and add it to the event
        _weakEventListener = new WeakEventListener<FixedControl, object, NotifyCollectionChangedEventArgs>(this);
        _weakEventListener.OnEventAction = (instance, source, eventArgs) =>
            instance.OnCollectionChanged(source, eventArgs);
        _weakEventListener.OnDetachAction = (weakEventListener) =>
            newNotifyCollectionChanged.CollectionChanged -= weakEventListener.OnEvent;
        newNotifyCollectionChanged.CollectionChanged += _weakEventListener.OnEvent;
    }
}

Note that the basic structure of the code is unchanged - there's just a bit more bookkeeping involved in creating and hooking up the WeakEventListener. The first thing to look at is the bottom half of the method where a new collection is handled - a WeakEventListener instance is created and a couple of properties are set. The OnEventAction property specifies a strongly-typed (thanks to WeakEventListener being generic!) function that gets called when the event is fired - this maps to whatever method would otherwise be used to handle the event. The OnDetachAction property specifies a strongly-typed function that gets called when the WeakEventListener is detached from the event it's listening to - it simply removes the WeakEventListener's handler for the event. (Note that this function takes a WeakEventListener parameter which should be used to unhook the event so as to prevent creating a closure that itself contains a hidden reference that could cause a leak. And if that last sentence makes no sense, don't worry about it - just follow the pattern shown here and you'll be fine.) Now that everything's in place, the WeakEventListener is attached to event of interest. Going back to the top half of the function, all that needs to be done is to manually detach the WeakEventListener from the event. The example also sets the WeakEventListener reference to null - but this is done for clarity and debugging convenience and not because it's necessary.

That's all there is to it - WeakEventListener adds only a couple of lines of code and saves gobs of aggravation!

Aside: People who prefer not to use anonymous methods like I've done here are welcome to write explicit methods to do the same thing. It's a few more lines of code and a tiny bit more work, but if you go that route, you can create static methods which almost guarantee you won't inadvertently create a leaky closure. Either way you do it, WeakEventListener works the same.

Finally, here's the complete implementation of WeakEventListener for anyone who's curious how it works:

/// <summary>
/// Implements a weak event listener that allows the owner to be garbage
/// collected if its only remaining link is an event handler.
/// </summary>
/// <typeparam name="TInstance">Type of instance listening for the event.</typeparam>
/// <typeparam name="TSource">Type of source for the event.</typeparam>
/// <typeparam name="TEventArgs">Type of event arguments for the event.</typeparam>
internal class WeakEventListener<TInstance, TSource, TEventArgs> where TInstance : class
{
    /// <summary>
    /// WeakReference to the instance listening for the event.
    /// </summary>
    private WeakReference _weakInstance;

    /// <summary>
    /// Gets or sets the method to call when the event fires.
    /// </summary>
    public Action<TInstance, TSource, TEventArgs> OnEventAction { get; set; }

    /// <summary>
    /// Gets or sets the method to call when detaching from the event.
    /// </summary>
    public Action<WeakEventListener<TInstance, TSource, TEventArgs>> OnDetachAction { get; set; }

    /// <summary>
    /// Initializes a new instances of the WeakEventListener class.
    /// </summary>
    /// <param name="instance">Instance subscribing to the event.</param>
    public WeakEventListener(TInstance instance)
    {
        if (null == instance)
        {
            throw new ArgumentNullException("instance");
        }
        _weakInstance = new WeakReference(instance);
    }

    /// <summary>
    /// Handler for the subscribed event calls OnEventAction to handle it.
    /// </summary>
    /// <param name="source">Event source.</param>
    /// <param name="eventArgs">Event arguments.</param>
    public void OnEvent(TSource source, TEventArgs eventArgs)
    {
        TInstance target = (TInstance)_weakInstance.Target;
        if (null != target)
        {
            // Call registered action
            if (null != OnEventAction)
            {
                OnEventAction(target, source, eventArgs);
            }
        }
        else
        {
            // Detach from event
            Detach();
        }
    }

    /// <summary>
    /// Detaches from the subscribed event.
    /// </summary>
    public void Detach()
    {
        if (null != OnDetachAction)
        {
            OnDetachAction(this);
            OnDetachAction = null;
        }
    }
}