Jungle Coder
this is technically an easter eggbug
Just added post themes! Witness The Colors, and Exult!

Performance gotchas in .NET: BindingList<T>

Every programming framework has certain corner cases that can drain performance when mishandled. The .Net Framework is no exception. I’ve discovered a few in my work with C#. I’ll be blogging about these as I find them.

These are not micro-optimizations like using String.Concat instead of += "" over a small string. These problems will suck the performance straight out of your application, and are inconspicuous in the code, requiring a some kind of profiling to realize what’s going on.

One of these potential performance sinks is the BindingList collection, often used in Windows Forms projects when the form needs to be data-bound against a list of objects. It’s a handy class to have at your disposal. It gives the same methods for collection handing as the List collection, and binds to ComboBoxes and ListBoxes very easily.

There’s only one caveat: when BindingList is used incorrectly, it gets used in a Shelmiel the Painter algorithm. But it only does this under a certain set of conditions:

The first condition is that an empty BindingList (I’ll call it nameList) is bound to a ComboxBox or ListBox on your form.

var nameList = new BindingList();ComboBox1.DataSource = nameList;

The second condition is that the BindingList is then populated in a loop or by using AddRange():

names = get10000MaleNamesFromFile();foreach(string n in names)
{
    nameList.Add(n);
}
//OR
nameList.AddRange(names);

Put these two together, and you get really bad performance. For 1000 names, it takes around 0.2-0.1 seconds. For 5000 names, it takes a little over 2 seconds. For 15000 names, it takes my computer somewhere on the order of 20 seconds to add the list(see the download at the end to test on your machine). That gets bad if I every had to deal with more than about 5000 names, but it will also get worse if the collection in question has a lot of properties that are accessed, such that even 500 items could be too many.

What’s going on here is an unseen inner loop with the call to NameList.Add(). In order to make bindings work, the BindingList class exposes an event called ListChanged, which provides two parameters to methods that handle it: An object reference that points to the BindingList, and a ListChangedEventArg that gives some information about the list change. It seems that ComboBoxes and ListBoxes enumerate the entire BindingList every time that the ListChanged event is fired. (This is effect is difficult to log, I’ll update if I get a code sample that does so).

There are two approaches to fix this:

1) Don’t set the BindingList as the Data Source of a Combo- or ListBox until the BindingList is completely filled. This keeps the ComboBox and ListBox from enumerating over items multiple times and taking a long time.

2) Turn off the ListChanged events while adding a lot of items to the BindindList. Using the example above, this approach is coded like so:

names = get10000MaleNamesFromFile();//Turn off the ListChanged Events
NameList.RaiseListChangedEvents = false;
foreach(string n in names)
{
    NameList.Add(n);
}
//Turn them back on.
NameList.RaiseListChangedEvents = true
//Notify the changes to the ComboBox
NameList.ResetBindings();

Either of these approaches takes the amount of time into the millisecond range, where 15000 records takes less time than the list of 1000 names did before. Now the code runs with linear performance. For anyone that wants to experiment with this performance issue, I wrote a small Windows Form that demonstrates the issue: you can download it here.

Published July 27th, 2013

Comments

Peter
BindingList<T> has another performance problem. If the items in the list support INotifyPropertyChanged then the list will listen for their PropertyChanged events and report them via a ListChanged event where ListChangedType == ItemChanged. The ListChangedEventArgs have to be populated with the index of the item in questions, so the list performs a linear scan to find it. The BindingList<T> has a slight optimisation where it will remember the index of the last item that raised PropertyChanged, so it can avoid consecutive searches for the same item (assuming that its index in the list has not changed). However, if you were to loop through the list and change a property on each item, each change would trigger a linear scan whose size depends on that item's position.
Previously: The People you Play with...
Next: Mechanical and Creative Work