Weak Events

I’ve blogged before about the well known issue where events in .NET can lead to memory leaks due to the strong reference the InvocationList maintains to any listeners and how WPF introduced the WeakEventManager class as a solution to this problem. There are a few problems with WeakEventManager, however. The first is one of performance. Periodically the WeakEventManager must step through it’s list of known listeners and “purge” them if the target no longer exists. This purging process in some situations can be very computationally expensive, impacting the performance of your application. Then there’s just the sheer drudgery involved in creating new WeakEventManager types for new event types and implementing IWeakEventListener on all target objects.

Well, I’ve got a solution to both problems. Let me introduce you to a WeakEventListener base class.

    public abstract class WeakEventListener<TSource, TArgs> : IDisposable
        where TArgs : EventArgs
    {
        private readonly WeakReference weakSource;
        private readonly WeakReference weakTarget;
        private readonly MethodInfo method;

        protected WeakEventListener(TSource source, Delegate handler)
        {
            this.weakSource = new WeakReference(source);
            if (handler.Target != null)
            {
                this.weakTarget = new WeakReference(handler.Target);
            }

            this.method = handler.Method;
        }

        ~WeakEventListener()
        {
            this.Dispose(false);
        }

        public void Dispose()
        {
            this.Dispose(true);
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposing)
        {
            object source = this.weakSource.Target;
            if (source != null)
            {
                this.StopListening((TSource)source);
            }
        }

        protected abstract void StopListening(TSource source);

        protected void OnEvent(object sender, TArgs args)
        {
            object target = null;
            if (this.weakTarget != null)
            {
                target = this.weakTarget.Target;
                if (target == null)
                {
                    this.Dispose();
                    return;
                }
            }

            this.method.Invoke(target, new object[] { sender, args });
        }
    }

Much like with the WeakEventManager we’ll need to implement a WeakEventListener for every event type we want to listen to, but it’s a whole lot easier to implement.

    public class PropertyChangedEventListener
        : WeakEventListener<INotifyPropertyChanged, PropertyChangedEventArgs>
    {
        public PropertyChangedEventListener(
            INotifyPropertyChanged source,
            PropertyChangedEventHandler handler)
            : base(source, handler)
        {
            source.PropertyChanged += this.OnEvent;
        }

        protected override void StopListening(INotifyPropertyChanged source)
        {
            source.PropertyChanged -= this.OnEvent;
        }
    }

Listening to events with the WeakEventListener is a whole lot easier than using an IWeakEventListener as well.

        private readonly IDisposable listener;

        public Target(Source source)
        {
            this.listener = new PropertyChangedEventListener(
                source,
                this.source_PropertyChanged);
        }

Simply create a new instance of the WeakEventListener and be sure to keep a reference to it (in the example I did so as an IDisposable reference). When you no longer want to listen to the event simply Dispose of the listener.

Using the WeakEventListener the event never maintains a strong reference to the target. It does maintain a strong reference to the WeakEventListener but this is likely a much smaller object than the target object. Further, if you fail to Dispose the WeakEventListener the strong reference to the WeakEventListener will be removed the next time the event is raised, so there’s never any “memory leak”.

I think the usage of this WeakEventListener is much easier than using a WeakEventManager and IWeakEventListener. It also should perform better in some situations, and probably at least as well in the rest. I’d be interested in hearing your thoughts on this.

2 Comments

  • Daniel Marbach said

    Hy
    When do you suggest to use the WeakEventListener instead of normal .Net events?

    Daniel

  • wekempf said

    Just to be clear, the WeakEventListener is used with normal .NET events. Just like the WeakEventManager/IWeakEventListener you should use WeakEventListener when the source can outlive the target.

Add a Comment