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.