Event subscription using weak references

In my previous post, I never really explained why it can be important to unsubscribe from events.

It is often the case that the “subject” (the object with the event) has a longer lifetime than the “observer” (the object that subscribes to the event). When we are no longer using the observer, we would like it to be garbage collected; however, if the observer is still subscribed to an event on the subject, the associated event handler holds a strong reference to the observer, so the observer will not be garbage collected until the subject also becomes garbage, or until the observer unsubscribes.

However, it is sometimes impossible (or very inconvenient) to determine the lifetime of an object, and so it isn’t possible to know when it would be safe to unsubscribe from the event. In that case, it would be really nice if we could subscribe to the event with a “weak delegate” - an event handler that would allow the target to be garbage collected.

Greg Schechter has the best article that I could find on this subject, and I encourage you to read it for more information. (His “containee” is our “subject” and his “container” is our “observer”.)

By using the EventInfo class from the previous post, we have created a method that allows a “weak subscription” to any event. Here’s an example, similar to the examples from that post.

    public sealed class Observer
    {
        public Observer(Subject subject)
        {
            Subject.NotifyEvent.WeakSubscribe(subject, this,
                (t, s, e) => t.UpdateSubject(subject));
        }
        private void UpdateSubject(Subject subject)
        {
            // ...

        }
    }

Note that the Observer class is not disposable. Also note that the delegate passed to WeakSubscribe does not hold a strong reference to the Observer. The WeakSubscribe method is passed a strong reference to the Observer via “this”, but it only ends up holding a weak reference to that reference.

For reasons that will (hopefully) become clear, we define WeakSubscribe as an extension method; in this form, it only works with events that use the simple EventHandler delegate:

    public static Scope WeakSubscribe<TSource, TTarget>(
        this EventInfo<TSource, EventHandler> info,
        TSource source, TTarget target, Action<TTarget, object, EventArgs> action)
            where TTarget : class
    {
        WeakReference weakTarget = new WeakReference(target, false);
        EventHandler handler = null;
        handler =
            (s, e) =>
            {
                TTarget t = (TTarget) weakTarget.Target;
                if (t != null)
                    action(t, s, e);
                else
                    info.RemoveHandler(source, handler);
            };
        return info.Subscribe(source, handler);
    }

Note how the actual event handler only calls the supplied delegate if the weak reference to the target is still valid. If it is not, the handler is removed, since it is no longer necessary.

This method also returns a Scope that unsubscribes from the event, for cases where the client wants to unsubscribe before the target is garbage collected.

We can support WeakSubscribe on events that use EventHandler with just a few minor changes:

    public static Scope WeakSubscribe<TSource, TTarget, TEventArgs>(
        this EventInfo<TSource, EventHandler<TEventArgs>> info,
        TSource source, TTarget target,
        Action<TTarget, object, TEventArgs> action)
            where TTarget : class
            where TEventArgs : EventArgs
    {
        WeakReference weakTarget = new WeakReference(target, false);
        EventHandler<TEventArgs> handler = null;
        handler =
            (s, e) =>
            {
                TTarget t = (TTarget) weakTarget.Target;
                if (t != null)
                    action(t, s, e);
                else
                    info.RemoveHandler(source, handler);
            };
        return info.Subscribe(source, handler);
    }

Finally, we can support WeakSubscribe on events that use any standard event handler (e.g. CancelEventHandler) by using DelegateUtility.Cast:

    public static Scope WeakSubscribe<TSource, TTarget, TEventArgs,
    TEventHandler>(
        this EventInfo<TSource, TEventHandler> info,
        TSource source, TTarget target,
        Action<TTarget, object, TEventArgs> action)
            where TTarget : class
            where TEventHandler : class
            where TEventArgs : EventArgs
    {
        WeakReference weakTarget = new WeakReference(target, false);
        TEventHandler handler = null;
        Action<object, TEventArgs> fn =
            (arg1, arg2) =>
            {
                TTarget t = (TTarget) weakTarget.Target;
                if (t != null)
                    action(t, arg1, arg2);
                else
                    info.RemoveHandler(source, handler);
            };
        handler = DelegateUtility.Cast<TEventHandler>(fn);
        return info.Subscribe(source, handler);
    }

All told, these methods make it very easy to “weakly” subscribe to events from any source. We welcome any comments, questions, or criticisms, as always!

Posted by Ed Ball on August 25, 2008