Search code examples
wpfdata-bindingweak-events

Do WPF controls use weak events in their bindings?


When I use databinding in WPF, my target controls are listening for events on the binding source. For example, I may have a ListView listening for CollectionChanged events on a ObservableCollection.

If the lifetime of an event source is expected to exceed the lifetime of an event listener, there is a potential memory leak, and the weak event pattern should be used.

Does WPF databinding follow the weak event pattern? If my ObservableCollection lives longer than my ListView, will my ListView be garbage collected?


Here is why I suspect that WPF controls do not implement the weak event pattern. If they did, I would expect both DerivedListView Collected! and DerivedTextBlock Collected! to be output to the console. Instead, only DerivedTextBlock Collected! is.

After fixing a bug in the code, both objects are collected. I'm not sure what to think.

Window1.xaml.cs

using System;
using System.Collections.ObjectModel;
using System.Windows;
using System.Windows.Controls;

namespace LeakDetector
{
    public class DerivedListView : ListView
    {
        ~DerivedListView()
        {
            Console.WriteLine("DerivedListView Collected!");
        }
    }

    public class DerivedTextBlock : TextBlock
    {
        ~DerivedTextBlock()
        {
            Console.WriteLine("DerivedTextBlock Collected!");
        }
    }

    public partial class Window1 : Window
    {
        // The ListView will bind to this collection and listen for its
        // events. ObColl will hold a reference to the ListView.
        public ObservableCollection<int> ObColl { get; private set; }

        public Window1()
        {
            this.ObColl = new ObservableCollection<int>();
            InitializeComponent();

            // Trigger an event that DerivedListView should be listening for
            this.ObColl.Add(1);

            // Get rid of the DerivedListView
            this.ParentBorder.Child = new DerivedTextBlock();

            GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);
            GC.WaitForPendingFinalizers();
            GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);

            this.ParentBorder.Child = null;

            GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);
            GC.WaitForPendingFinalizers();
            GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);

            Console.WriteLine("Done");
        }
    }
}

Window1.xaml

<Window
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:LeakDetector"
    x:Class="LeakDetector.Window1"
    DataContext="{Binding RelativeSource={RelativeSource Self}}"
    Height="300" Width="300"
    Title="Leak Detector">
    <Border x:Name="ParentBorder">
        <local:DerivedListView ItemsSource="{Binding Path=ObColl}" />
    </Border>
</Window>

Solution

  • In essence, the WPF controls themselves do not have anything to do with weak events. Instead, there are certain classes related to WPF's Binding engine that implement the weak event pattern. The class PropertyChangedEventManager implements WeakEventManager. And if you use Reflector, you'll see that several classes implement IWeakEventListener in the MS.Internal.Data namespace (one in particular is the MS.Internal.Data.PropertyPathWorker class which directly uses the PropertyChangedEventManager). These objects are used by WPF internally to do Data Binding.

    ItemsControls and CollectionChanged events are a different story and has nothing to do with Bindings. See, you could do something like "listView.ItemsSource = myObservableCollection" in the code behind and the collection-changed notification will still work. No Binding objects are involved here at all. Here, a different set of "weak-event-related classes" are in play. ItemCollection and ItemContainerGenerator implement IWeakEventListener, and they work in conjunction with the CollectionChangedEventManager(which implements WeakEventManager).