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