Unsubscribing from C# events

Unsubscribing from C# events can be a pain. It isn’t so bad when your event handler is a simple static or instance method, because those methods are still available when you’re ready to unsubscribe.

    public sealed class Subject
    {
        public event EventHandler Notify;
        // ...

    }
    public sealed class Observer : IDisposable
    {
        public Observer(Subject subject)
        {
            m_subject = subject;
            m_subject.Notify += Subject_Notify;
        }
        public void Dispose()
        {
            m_subject.Notify -= Subject_Notify;
        }
        private void Subject_Notify(object sender, EventArgs e)
        {
            // ...

        }
        readonly Subject m_subject;
    }

When you subscribe to an event using an anonymous delegate, however, things get trickier. You’ve got to keep that delegate around so that you can remove it from the event.

    public sealed class Observer : IDisposable
    {
        public Observer(Subject subject)
        {
            m_subject = subject;
            m_handler = delegate { UpdateSubject(); };
            m_subject.Notify += m_handler;
        }
        public void Dispose()
        {
            m_subject.Notify -= m_handler;
        }
        private void UpdateSubject()
        {
            // ...

        }
        readonly Subject m_subject;
        readonly EventHandler m_handler;
    }

The Scope class can be a convenient way to encapsulate the lifetime of an event, though it is a bit awkward, because you have to declare a local variable for the event handler.

    public sealed class Observer : IDisposable
    {
        public Observer(Subject subject)
        {
            EventHandler handler = delegate { UpdateSubject(subject); };
            subject.Notify += handler;
            m_scope = Scope.Create(() => subject.Notify -= handler);
        }
        public void Dispose()
        {
            m_scope.Dispose();
        }
        private void UpdateSubject(Subject subject)
        {
            // ...

        }
        readonly Scope m_scope;
    }

We’ve written an EventInfo class that encapsulates the add/remove behavior of any event and makes it easy to create a Scope that unsubscribes to an event.

    public sealed class EventInfo<TSource, TEventHandler>
    {
        public EventInfo(Action<TSource, TEventHandler> fnAddHandler,
            Action<TSource, TEventHandler> fnRemoveHandler)
        {
            m_fnAddHandler = fnAddHandler;
            m_fnRemoveHandler = fnRemoveHandler;
        }
        public void AddHandler(TSource source, TEventHandler handler)
        {
            m_fnAddHandler(source, handler);
        }
        public void RemoveHandler(TSource source, TEventHandler handler)
        {
            m_fnRemoveHandler(source, handler);
        }
        public Scope Subscribe(TSource source, TEventHandler handler)
        {
            AddHandler(source, handler);
            return Scope.Create(() => RemoveHandler(source, handler));
        }
        readonly Action<TSource, TEventHandler> m_fnAddHandler;
        readonly Action<TSource, TEventHandler> m_fnRemoveHandler;
    }

Here’s how EventInfo would be used:

    public sealed class Subject
    {
        public event EventHandler Notify;
        public static readonly EventInfo<Subject, EventHandler> NotifyEvent =
            new EventInfo<Subject, EventHandler>(
                (s, eh) => s.Notify += eh, (s, eh) => s.Notify -= eh);
        // ...

    }
    public sealed class Observer : IDisposable
    {
        public Observer(Subject subject)
        {
            m_scope = Subject.NotifyEvent.Subscribe(subject,
                delegate { UpdateSubject(subject); });
        }
        public void Dispose()
        {
            m_scope.Dispose();
        }
        private void UpdateSubject(Subject subject)
        {
            // ...

        }
        readonly Scope m_scope;
    }

Any class with events can expose static read-only EventInfo fields to help clients with this pattern, but a client is certainly capable of creating EventInfo instances on its own, since the EventInfo doesn’t manage the process of raising the event at all.

If you’re using the Subscribe pattern a lot with a single event, the EventInfo class is probably worth using. However, the real reason we created EventInfo was to make it easier to subscribe to events with weak references, which will be the subject of my next post.

Posted by Ed Ball on August 21, 2008