WPF CollectionView can leak memory

By Mirek on (tags: CollectionView, memory leak, WPF, categories: code)

A time ago I wrote about using collection views in WPF to achieve different looks at the same source collection. By creating different instances of ListCollectionView we could have the collection displayed in different sorting, ordering and grouping shape. Unfortunately there is small trap when using collection views.

CollectionView class instantiated with a source collection that implements the INotifyCollectionChanged interface (ObservableCollection<> for instance), benefits from this functionality. That means any changes made to the source collection are automatically reflected in the view. The problem now is that the CollectionView holds internally a strong event handler reference to the source collection’s CollectionChanged event. This basically prevents the collection view to be collected by a GC as long as the original source collection is alive. To reproduce the memory leak try to run below code.

   1: ObservableCollection<object> source = new ObservableCollection<object>();
   2: WeakReference weakRef = null;
   4: private void TestMemory()
   5: {
   6:     CreateView();
   7:     GC.Collect();
   8:     GC.WaitForPendingFinalizers();
   9:     bool isalive = weakRef.IsAlive;
  10: }
  12: public void CreateView()
  13: {
  14:     ListCollectionView view = new ListCollectionView(source);
  15:     weakRef = new WeakReference(view);
  16: }

We have the observable collection named ‘source’ as a source collection and weak reference ‘weakRef’ to monitor if the collection view is garbage collected. Now in the scope of CreateView() method we create a local instance of ListCollectionView based on our global instance of observable collection. After leaving this method and requesting garbage collection (lines 7 and 8) we expect that created view is freed. Unfortunately line 9 says something different.
This is a known problem, or rather an inconvenience, since this is a consequence of other WPF functionality. The issue is registered (and already closed) on Microsoft pages where you can read more about it (link).

One of solutions to this problem is to explicitly detach the view from the source collection. We can achieve this by calling DetachFromSourceCollection(); method.

   1: public void CreateView()
   2: {
   3:     ListCollectionView view = new ListCollectionView(source);
   4:     weakRef = new WeakReference(view);
   5:     view.DetachFromSourceCollection();
   6: }

Another one is, as suggested as workaround on linked Microsoft issue entry, to subclass a ListCollectionView and replace a strong event handler reference with a weak handler reference. This is my favorite solution

   1: public class ListCollectionViewEx : ListCollectionView, IWeakEventListener
   2: {
   3:     public ListCollectionViewEx(IList list)
   4:         : base(list)
   5:     {
   6:         INotifyCollectionChanged changed = list as INotifyCollectionChanged;
   7:         if (changed != null)
   8:         {
   9:             changed.CollectionChanged -= this.OnCollectionChanged;
  10:             CollectionChangedEventManager.AddListener(changed, this);
  11:         }
  12:     }
  14:     public bool ReceiveWeakEvent(Type managerType, object sender, EventArgs e)
  15:     {
  16:         if (!(e is NotifyCollectionChangedEventArgs)) return false;
  17:         this.OnCollectionChanged(sender, (e as NotifyCollectionChangedEventArgs));
  18:         return true;
  19:     }
  20: }

Now simply using ListCollectionViewEx instead of ListCollectionView we can forget about the memory leak Puszczam oczko

   1: public void CreateView()
   2: {
   3:     var view = new ListCollectionViewEx(source);
   4:     weakRef = new WeakReference(view);
   5: }