C# – Weak Event Handlers

Hammering in My Head: Garbage

I’m going to take a brief break from my series on MVVM to discuss an aspect of WPF development that recently bit me on the bum. When dealing with event handlers in C#, it is important to take account of the relative lifetimes of the source and listener objects.

In WPF / MVVM, the most common event we use is INotifyPropertyChanged.PropertyChanged – either in Xaml bindings or created manually in code behind. In most instances, PropertyChanged bindings are between a View and ViewModel pair which are created and destroyed together, but that’s not always the case. The demo project for this post demonstrates this asymmetry in action.

The MainView contains a timer which updates the current date/time and the allocated memory (as reported by the GarbageCollector) each second. The requirement is to create a number of child windows, observing what happens to the memory usage as we open and close them. To ensure that the memory hit is obvious, the ChildViewModel creates an (otherwise unused) array of ten million integers. There are three design cases to consider.

A. No link between main and child views

This is the simplest case – just create an new ChildView with a corresponding ChildViewModel and show it …

var childView = new ChildView();
var childViewModel = new ChildViewModel();
childView.DataContext = childViewModel;
childView.Show();

As each child window is created, the allocated memory increases as expected. When a child window is closed, the allocated memory remains the same, but that’s not a total surprise. In C#, when an object goes out of scope, the memory it used is not immediately reclaimed and released. That process is handled by the GarbageCollector. Normally garbage collection runs automatically in the background, but in order to see an immediate effect we have to manually force it by calling GC.Collect(). After clicking on the button you will see the allocated memory drop again. OnForceGarbageCollect() actually calls GC.Collect() twice to ensure maximum memory reclaim each time.

B. A link between main and child views / viewmodels

What if we add a new requirement – to have the current date / time, as displayed on the main view, to be appear on each child window as well. That shouldn’t be too hard – we can create a CurrentDateTime property on the ChildViewModel and set up a PropertyChanged handler to update it each time the CurrentDateTime property on the MainViewModel is updated.

var childView = new ChildView();
var childViewModel = new ChildViewModel();
PropertyChanged += (s, e) =>
{
    if (nameof(CurrentDateTime).Equals(e.PropertyName))
        childViewModel.CurrentDateTime = CurrentDateTime);
};
childView.DataContext = childViewModel;
childView.Show();

All looks fine – the child windows open as expected and the CurrentDateTime values are synchronised, and the allocated memory value increases as before. However, look at what happens when we close a child window – no matter how many times we force garbage collection, the allocated memory doesn’t decrease! What’s going on? As you might expect it’s the new PropertyChanged event handler that is causing the problem. The event handler creates a reference back from the MainViewModel to the ChildViewModel, which is sufficient to make the garbage collector think that the ChildViewModel is still in active use and therefore not reclaim its memory, even though the corresponding View has been closed. In this case, the ChildViewModel objects remain uncollected until the application is closed – the call to Debug.WriteLine() in the ChildViewModel destructor confirms this. The memory usage in this demo application is a little extreme just to make a point, but if you have a long running application you will still be leaking memory if you use this construct.

C. A link between main and child views / viewmodels – doing it the right way

Now that we’ve identified the cause of the issue, how do we resolve it? The solution is through the use of WeakReferences. These allow a reference to an object to be maintained that won’t block garbage collection if there are no other live references. This part of my library was inspired by a 2009 blog post by Andy Kutruff: High Performance Property Changed Weak Event Notifications for C#, which I’ve updated to use some more modern C# constructs. perWeakPropertyChangedEventHandler handles the PropertyChanged event links between source and listener objects, using weak references for both.

The static perWeakPropertyChangedEventHandler class can handle many INotifyPropertyChanged source objects

… each of which can have many properties being observed

… each of which can have many listener objects

… each of which has one handler action to invoke when that property changes.

The internal collections will clear down any stale entries after a source or listener object is garbage collected. The handler action might look a little strange with three parameters, but it is done this way to prevent the creation of a closure or any other solid reference to the listener during registration.

Now we can use this manager to maintain the PropetyChanged event handlers between MainViewModel and each ChildViewModel …

var childView = new ChildView();
            var childViewModel = new ChildViewModel();
            perWeakPropertyChangedEventHandler.Register(this, 
                                                        nameof(CurrentDateTime), 
                                                        childViewModel, 
                                                        (l, s, e) => l.CurrentDateTime = CurrentDateTime);
            childView.DataContext = childViewModel;
            childView.Show();

Now when we run the application and create child windows, the allocated memory increases and the CurrentDateTime values remain in sync as expected. This time however, when a child window is closed the allocated memory correctly decreases again (after forcing garbage collection) – happy days 🙂

You might be asking if you need to use this construct for all PropertyChanged handlers, including Xaml data binding. The answer is no. As I already started, the View and ViewModel objects usually co-exist as a pair, so managing their relative lifespans is not necessary. The main usage for this technique is where you have an imbalance – especially when the source object can exist for the whole application lifetime.

In my next post, I’ll resume my discussion on MVVM by looking at commands, which will include usage of this perWeakPropertyChangedEventHandler class.

Don’t forget that all of the code samples for this blog are available on Github, along with my own personal C# / WPF library.

Leave a Reply

Your email address will not be published. Required fields are marked *