ListView may leak with ImageLists

Today, I encountered a problem with WinForm’s ListView imagelist. When you assign a global imagelist to the listview, then the listview will remain in memory forever. When the ListView references other objects, then these objects won’t be collected too and you might end up with a huge memory leak.

When does this occur?
ListViews won’t be collected until the associated imagelist(s) is collected, when you don’t reset theĀ SmallImageList, LargeImageList or StateImageList properties yourself.

Why does this occur?
The LargeImageList property is implemented like this:

public ImageList LargeImageList
{
 get { return this.imageListLarge; }
 set
 {
  if (value != this.imageListLarge)
  {
   // Unsubscribe from the old image list events
   if (this.imageListLarge != null)
   {
    this.imageListLarge.RecreateHandle -= LargeImageListRecreateHandle;
    this.imageListLarge.Disposed -= DetachImageList;
    this.imageListLarge.ChangeHandle -= LargeImageListChangedHandle;
   }

   // Save new imagelist
   this.imageListLarge = value;

   // Subscribe to the new image list events
   if (value != null)
   {
    value.RecreateHandle += new EventHandler(LargeImageListRecreateHandle);
    value.Disposed += new EventHandler(DetachImageList);
    value.ChangeHandle += new EventHandler(LargeImageListChangedHandle);
   }

   // Update the underlying imagelist,
   // if the control is already created
   if (base.IsHandleCreated)
   {
    IntPtr imageListHandle = (value==null)?IntPtr.Zero:value.Handle;
    base.SendMessage(LVM_SETIMAGELIST, IntPtr.Zero, imageListHandle);
    if (this.AutoArrange && !this.listViewState[4])
     this.UpdateListViewItemsLocations();
   }
  }
 }
}

The implementation of SmallImageList and StateImageList are similar. You can clearly see that the ListView subscribes to the following three events of the ListView:

  • The RecreateHandle event is raised when the ImageList handle is recreated, because some of the properties have changed, that require that the underlying ImageList object to be recreated. The ListView responds to this event by setting the new ImageList to the underlying ListView object.
  • The Disposed event is raised when the ImageList is disposed. The ListView responds to this event by resetting the LargeImageList property to NULL.
  • The ChangeHandle event is an internal (and undocumented) event from the ImageList that is raised, when the ImageList is changed (i.e. adding or removing an image from the imagelist). The ListView responds to this event by setting the item images. Note that this event is only subscribed to for the LargeImageList property.

The basic idea of the garbage collector is that objects that aren’t referenced anymore are collected. As long as you reference an object it will stay alive. Subscribing to an object means that you reference the object via the event handler delegate. So, before the ListView can be collected, the reference to the imagelists should be removed by setting the properties to NULL. This will effectively unsubscribe from the three events.

When the ListView is disposed, then it should make sure that it unsubscribe from these events to prevent that the subscription keeps the ListView alive. The Dispose method is implemented like this:

protected override void Dispose(bool disposing)
{
 if (disposing)
 {
  // Reset small imagelist
  if (this.imageListSmall != null)
  {
   this.imageListSmall.Disposed -= DetachImageList;
   this.imageListSmall = null;
  }

  // Reset large imagelist
  if (this.imageListLarge != null)
  {
   this.imageListLarge.Disposed -= DetachImageList;
   this.imageListLarge = null;
  }

  // Reset state imagelist
  if (this.imageListState != null)
  {
   this.imageListState.Disposed -= DetachImageList;
   this.imageListState = null;
  }

  // [some other code removed]
  // ...

  // Call base class
  base.Dispose(disposing);
 }
}

As you can see, this method only unsubscribes from the Disposed event, but not from the imagelist’s RecreateHandle and ChangeHandle events. The garbage collector will keep the ListView alive, until the associated imagelists are collected. When you use a global imagelist, then this will never occur and each instance of the ListView that uses the imagelist will remain alive forever.

Microsoft can easily fix this problem by rewriting the Dispose method like this:

protected override void Dispose(bool disposing)
{
 if (disposing)
 {
  // Reset imagelists (note that we set the property, not the attribute)
  this.ImageListSmall = null;
  this.ImageListLarge = null;
  this.ImageListState = null;

  // [some other code removed]
  // ...

  // Call base class
  base.Dispose(disposing);
 }
}

For now the only thing that you can do is to make sure you reset the imagelist yourself before your ListView is disposed.

So how about TreeViews?
A TreeView also uses an imagelist, so it would make sense that the same problem exists there. Fortunately, the TreeView is fine, because the implementation uses two special methods to subscribe and unsubscribe from the three events. The Dispose method of the TreeView calls the unsubscribe method, so the TreeView is fine.

Sample application
I have created a sample application that clearly demonstrates the problem. I needed to override the ListView class to make sure I could count its instances, but it doesn’t have any other side effects.

To illustrate the problem, start the application and press the “Create form” button. This will create a ListView and populates it with some items. When you refresh the counters you’ll see that you have one instance and one active instance (this means an instance that hasn’t been disposed yet). If you close the form and refresh the counters again, then you’ll see that there is no active instance anymore, but this is still a ListView instance. This means that the form has been disposed, but hasn’t been collected. You can try this over and over, but you will never see the number of ListView instances drop.

When you enable the “Reset imagelists when disposing” checkbox in the main form, then the properties are reset and you’ll see that the number of instances will stay in sync with the number of active instances.
Download the sample application.

Response from Microsoft
I have submitted this bug into the MSDN Managed Newsgroups and the Microsoft Feedback website. Microsoft acknowledges this to be a problem and, when it fits the schedule, it will be fixed. The recently released .NET Framework 4 Beta 1 doesn’t include the fix.

Microsoft updated the status to resolved (closed) on June 7th, 2009, but didn’t provide additional information how and when the fix will be available.

Virtual collections (part 1)

In the era of Win32/MFC the virtual listbox (and virtual listview) was a popular method to efficiently display large collections. In WPF there is no standard way to efficiently display a large collection of items. For each control that is derived from ItemsControl (such as the ListBox) there are two important items in the control:

  1. The UI element that is used to display the item.
  2. The actual .NET object that is represented by the UI element.

Most ItemsControl derived classes use a VirtualizingStackPanel object to display the items in the control. This object only creates the UI elements that are actually displayed on screen. If you have a Listbox containing 10.000 elements and only some are displayed on screen, then VirtualizingStackPanel only creates the UI elements for the visible items. This sounds great, but the underlying collection still contains 10.000 elements. If you need to collect these elements from another tier (i.e. via a webservice), then it takes a very long time to populate the listbox.

In the next paragraphs I will describe the concept of “virtual collections” to efficiently populate the collection. A virtual collection is a collection containing only stubs to the actual items. If an item is accessed, then it is created on demand. If only the first 10 elements in a 10.000 item collection are accessed, then only the first 10 elements are actually created. This can greatly improve performance for large collections that are expensive to populate.

ItemsControl derived class uses an ItemsSource property that accepts an object that implements the IEnumerable interface. It is much more efficient to supply an object that also implements the IList interface, because it supports indexed access to the underlying collection. I haven’t found any documentation, but I found out that if your collection support IList, then the IList interface is used in favor of the (less efficient) IEnumerable interface. The virtual collection that is presented here supports the IList interface (and implicitly support IEnumerable as well). That’s why it is compatible with all ItemsSource derived controls.

The concept is easy. During the creation of the virtual collection an array is allocated that only contains null pointers. When the indexing operator [] is used, then the underlying object is created and stored inside the array. The next time the same item is accessed, then it is already there and doesn’t need to be created anymore.

The virtual collection uses a generator to create the underlying objects. The object generator should implement the IObjectGenerator interface and must be provided by the programmer. This interface only has one property and one method:

  • The Count property returns the number of items in the collection.
  • The CreateObject method creates a new object based on the given index.

It is very easy to use the virtual collection. Implement an object that implements IObjectGenerator<T> and pass it to the VirtualList<T>‘s constructor. Refer to the attached example code for an example how to use and bind the collection.

In a next part I will show you how to add paging to IObjectGenerator to reduce the number of roundtrips. This is useful for collections that are large and the roundtrip to create the objects is expensive.