Saturday, February 5, 2011

How To: Make Your View Models Disposable

This is actually a part two post to one of my earlier posts about using reflection and custom attributes to wire up dependent property change notifications.  Using anonymous methods for such a task works quite well, but I was hitting some roadblocks when trying to use that architecture with models and view models that need to implement IDisposable.

In order for an object to be cleaned up by garbage collection (GC), it cannot be subscribed to an event on an object that is still being held in memory.  The problem I was running into was that I had a couple specific view models that needed to be disposed of and made available for GC, but were still wired up to property changed (and collection changed) events on our business models, which were not being disposed of [intentionally, as they were still being used by the rest of the application].  

So my task at hand was to implement IDisposable in such a way that all event handlers were removed, so the object was no longer referenced in code, and GC could proceed.

Here are the major changes I made to my demo project:
//tracks events subscribed to so Dispose() can unsubscribe
private readonly List<Tuple<object, EventInfo, Delegate>> 
  _subscribedEvents = new List<Tuple<object, EventInfo, Delegate>>();

/// <summary> 
/// Register an event handler, handlers added using this method will be removed in Dispose() method. 
/// </summary> 
/// <param name="publisher">Object publishing event</param> 
/// <param name="eventName">name of event</param> 
/// <param name="handler">event handler to add</param> 
protected void RegisterEventHandler(object publisher, string eventName, Delegate handler) 
{
//already registered - return immediately
if (_subscribedEvents.Any(item => item.Item1.Equals(publisher) && item.Item2.Name.Equals(eventName) && item.Item3.Equals(handler))) return;


//get event info object
var eInfo = publisher.GetType().GetEvent(eventName);  
//add handler
eInfo.AddEventHandler(publisher, handler);


//track the subscription, so we can un-wire it when Dispose() is called
_subscribedEvents.Add(new Tuple<object, EventInfo, Delegate>(publisher, eInfo, handler)); 
}
/// <summary> 
/// Disposes of the object 
/// </summary> 
/// <param name="disposing"></param> 
protected virtual void Dispose(bool disposing) 
{
if (disposing)
{
//remove event handlers for subscribed events
foreach (var item in _subscribedEvents)
item.Item2.RemoveEventHandler(item.Item1, item.Item3);


_subscribedEvents.Clear(); //clear the subscribed events collection, so we're not referencing other objects either

}

Working project/demo download can be found here: