Fixing BindingList Deserialization

Save/Share Google Yahoo! Digg It Reddit del.icio.us
My Zimbio

I previously blogged about how to use the C# INotifyPropertyChanged interface with BindingList<T> instances so that your lists properly fire the ListChanged event with a ListChangedType of ItemChanged.

In that post, I also alluded to the fact that BindingList<T> is flawed with regard to serialization of BindingList<T> subtypes. The problem has to do with the fact that BindingList<T> listens for PropertyChanged events on any of its list items that implement INotifyPropertyChanged. However, because event handlers are not serializable, those listener connections are lost during the serialization process for subtypes, and they don’t get rewired when the list is deserialized. It’s straightforward to do that, but the implementation is missing from the .NET SDK, so we have to write it ourselves.

For a code example that shows how this flaw behaves, check out Demonstrating BindingList Serialization Bug.

Interestingly, this problem doesn’t show up with concrete instances of BindingList<T> — only its subtypes. [update: some readers report the problem still exists with concrete BindingLists – could it be explained by different patch levels on the framework?] Why would you ever want to extend BindingList<T>? Perhaps it’s to add domain specific features, or perhaps it’s to add generic sorting capabilities like the SortableBindingList<T> class described in this great article by Michael Weinhardt.

On my own projects, I use a fairly sophisticated BindingList<T> subtype that fixes a number of shortcomings. For this post, however, I’ll focus on the serialization issue.

What I’ve done is I’ve extended BindingList<T> and added a method annotated with the OnDeserialized attribute like this:

using System;

using System.Collections.Generic;

using System.ComponentModel;

using System.Runtime.Serialization;

namespace MyBindingList

{

[Serializable]

public class FixedBindingList<T> : BindingList<T>

{

[OnDeserialized]

private void OnDeserialized(StreamingContext context)

{

List<T> items = new List<T>(Items);

int index = 0;

// call SetItem again on each item to re-establish event hookups

foreach (T item in items)

{

// explicitly call the base version in case SetItem is overridden

base.SetItem(index++, item);

}

}

}

}

The StreamingContext parameter is a requirement of the OnDeserialized attribute, but everything else is straightforward. After .NET has finished deserializing the list, we iterate over each of the items and invoke SetItem(). These items, of course, are already on the list at this point. However, it’s the implementation of BindingList<T>.SetItem() that handles the wiring of event listeners to the PropertyChanged event of any items implementing INotifyPropertyChanged. Because we’re essentially “replacing” list items with references to the same item, there are no other side effects to the list.

One final note is to point out that I explicitly invoke the supertype implementation of SetItem() using the base keyword. SetItem() is a virtual method, and it’s possible that a more elaborate extension of BindingList<T> may choose to override it. That’s okay, but it might unintentionally introduce unwanted behavior on deserialization. Thus, we avoid that problem by forcing the call to be handled directly by the base class.

Save/Share Google Yahoo! Add to Technorati Favorites Digg It Reddit
del.icio.us My Zimbio

Leave a Reply