Jungle Coder

The musings of a third culture coder and missionary kid

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

Previously: The People you Play with...
Next: Mechanical and Creative Work

Comments

Nikola Malešević

Thanks so much, you saved me a lot of pain.

Daniel

Good one!

Phuong

Excellent

Mike

Thank you sooo much for this post. I've been having trouble understanding why my 2 lists of 150 items was lagging out my application so badly. This made the application go from taking 3 seconds for each change to under half a second.

Ian

It's a good idea to use a finally block to turn list changed events back on, so that you can be sure they don't stay turned off after an exception (that's handled elsewhere).

Tim

If you're using that approach multiple times, consider implementing an extension function for the BindingList: https://stackoverflow.com/a/47550007/4469336 This also has the finally-block which Ian suggested!


Email:*
Name:*
Reqired fields are marked with a '*'