Geeks With Blogs
New Things I Learned

One of my prior post talks about how CollectionView (and anything deriving from it like ListCollectionView) doesn't get garbage collected after use - even worse it continues to hang onto the data it was bound to, which may cause performance issues.  After fiddling through this, I found 3 ways to disconnect them, which I'll detail below.

- Derive classes from CollectionView that can dispose itself.

The subscriber to the CollectionChanged event is a protected function named 'OnCollectionChanged' - so the code is fairly trivial as shown below:

public class CollectionViewEx : CollectionView, IDisposable

{

   public CollectionViewEx(IEnumerable enumerable)

      : base(enumerable)

   {

   }

 

   ~CollectionViewEx()

   {

      Dispose(false);

   }

 

   #region IDisposable Members

 

   public void Dispose()

   {

      Dispose(true);

   }

 

   private bool _disposed = false;

   protected virtual void Dispose(bool disposing)

   {

      if (_disposed == false)

      {

         if (disposing)

         {

            // Dispose managed resources

            INotifyCollectionChanged ncc = SourceCollection as INotifyCollectionChanged;

            if (ncc != null)  // Unsubscribe from CollectionChanged event

               ncc.CollectionChanged -= this.OnCollectionChanged;

         }

 

         // Dispose unmanaged resource

         _disposed = true;

      }

   }

 

   #endregion

}

I'm using the Dispose Finalize pattern but left out suppressing Finalize so I can break through the code and verify that the garbage collector hits the finalizer.  New classes however also means that you have to create a new class for each type of CollectionView that you want to use.  Even worse, you can't use it with CollectionViewSource.GetDefaultView() since that method will return one of the .NET classes, but not yours.

- Create a collection class that can remove the CollectionChanged subscriber

public interface IRemoveCollectionChangedSubscriber

{

   bool RemoveCollectionChangedSubscriber(object subscriber);

}

 

public class MyObservableCollection<T> : ObservableCollection<T>,

   IRemoveCollectionChangedSubscriber

{

   public override event NotifyCollectionChangedEventHandler CollectionChanged;

 

   #region IRemoveCollectionChangedSubscriber Members

 

   public bool RemoveCollectionChangedSubscriber(object subscriber)

   {

      NotifyCollectionChangedEventHandler _event = CollectionChanged;

      if (_event == null)  // No subscriber

         return false;

 

      // Go through handler

      foreach (NotifyCollectionChangedEventHandler handler in _event.GetInvocationList())

      {

         if (object.ReferenceEquals(handler.Target, subscriber))

         {

            CollectionChanged -= handler;

            return true;

         }

      }

 

      return false;

   }

 

   #endregion

}

It is fairly simple code, I decided to create an interface that defines a method that can remove a subscriber, that way I can use the same interface in any other collection classes.  However, this also meant that I can't use the typical collection classes as is - I have to derive from it.  The consumer code is fairly simple; the consumer code is being called to remove the subscriber when a view is closed.

protected override void OnClosed(EventArgs e)

{

   base.OnClosed(e);

   DetachCollectionAndView(_someCollectionView);

}

 

private bool DetachCollectionAndView(CollectionView cv)

{

   IRemoveCollectionChangedSubscriber remover = cv.SourceCollection as IRemoveCollectionChangedSubscriber;

   return (remover != null) ? remover.RemoveCollectionChangedSubscriber(cv) : false;

}

Since the subsciber is the collection view itself, I pass the CollectionView object to the method.

- Use reflections

private bool DetachCollectionAndView(CollectionView cv)

{

   INotifyCollectionChanged ncc = cv.SourceCollection as INotifyCollectionChanged;

   if (ncc == null)

      return false;

 

   // Get the method that subscribes to OnCollectionChanged

   MethodInfo mi = cv.GetType().GetMethod("OnCollectionChanged",

      BindingFlags.NonPublic | BindingFlags.Instance, null,

      new Type[] { typeof(object), typeof(NotifyCollectionChangedEventArgs) },

      null);

   NotifyCollectionChangedEventHandler handler = (NotifyCollectionChangedEventHandler)

      Delegate.CreateDelegate(typeof(NotifyCollectionChangedEventHandler), cv, mi);

   collection.CollectionChanged -= handler;

 

   return true;

}

We know that the subscriber method is named 'OnCollectionChanged', it is protected, so we can search for this.  That function has an overload, so we have to provide the types used in the method to get the proper one using reflection.  Once we get it, then we create a delegate out of it and remove it out from the event by using the -= operator.  This approach is actually very straightforward, but it uses reflections, which people may want to avoid.  However, this approach requires the least amount of coding - you can still use the CollectionView class (or any of its derived classes), use WPF constructs that will return those classes, and also use any collection/list classes that you already use.

All in all, none of these would be necessary, if only Microsoft has implemented IDisposable in the CollectionView class, or even just provide a method to detach the collection.

Posted on Thursday, February 7, 2008 6:08 PM WPF , .NET | Back to top


Comments on this post: Disposing CollectionView (Detaching your data and the CollectionView)

No comments posted yet.
Your comment:
 (will show your gravatar)


Copyright © Muljadi Budiman | Powered by: GeeksWithBlogs.net