Last week, we noticed our data grid (an Infragistics UltraGrid) was behaving very slow during data population and filtering. At first I assumed we were doing something wrong in our code to cause the slow down. After a few minutes of poking around I did not see anything obvious. Someone noticed the equals method was called frequently when the data is being populated in the grid. It seems like the equals method was being invoked when the BindingSource is being set. I decided to do a simple little experiment to figure out what was going on.
class Program {
public static int NumberOfItemsAdded = 1000;
static void Main(string[] args) {
BindingSource bindingSource = new BindingSource();
List<User> users = new List<User>();
for (int i = 0; i < NumberOfItemsAdded; i++) users.Add(new User());
bindingSource.DataSource = users;
Console.WriteLine("Equals was called: " + User.NumOfTimesEqualsCalled);
Console.ReadLine();
}
}
internal class User {
public static int NumOfTimesEqualsCalled = 0;
public override bool Equals(object obj) {
NumOfTimesEqualsCalled++;
return base.Equals(obj);
}
}
I started out with 1000 objects in my list then set it to the binding source. The equals method was not called once. This seemed weird to me because in our application the equals method is definitely being called. I decided to add a property to the User class and see what happens.
public string Prop1 {
get { return ""; }
}
By adding a property to the User class, the equals method is being invoked 4 times. I added a couple more properties and, sure enough, the equals method is being called 4 times per property. If you have 10 properties, the equals will be called 40 times. I decided to change the implementation of the equals method to see if that would change number of times being invoked.
public override bool Equals(object obj) {
NumOfTimesEqualsCalled++;
return true;
}
Nothing seemed to change when the equals is always returning true. How about the opposite?
public override bool Equals(object obj) {
NumOfTimesEqualsCalled++;
return false;
}
When equals is always returning false it became a little bit more interesting Now it calls equals on multiples of 18 times per property. It is not realistic for equals to always return false, so I decided to try a mixture of both, half true and half false.
public override bool Equals(object obj) {
NumOfTimesEqualsCalled++;
Random random = new Random();
int randomInt = random.Next();
return randomInt % 2 == 0;
}
This time it was not so straight forward with the number of time the equals method is being invoked. With one property it is called 13 times. With two properties, it is called 21 times. From this point on, it seems to be multiples of 4 times per property. What happens if equals return a value randomly? It depends on how many properties you have when you set it to be a binding source. If you are lucky, the equals method will be invoked 4 times per property. If you have 10 properties, it will be called 40 times. But in many cases it could be a lot worse. The equals could be invoked 18 times per property which is 180 times if you have 10 properties. All that work is done just to set the binding source’s data source. It is not even displaying in the grid.
In my example I created a list with 1000 objects, but that actually does not matter. The list could contain one object and the outcome will be the exactly same. In our application the slow down is more exaggerated because we are using reflection to perform the equals. The only reason we are using reflection is because we only use equals to perform dirty checking on the object after the user has performed an edit. This is not a performance issue because at most you are only doing reflection on object. However, if the binding source is calling it hundreds of times, this becomes an issue. Since we are only using equals for dirty checking, we can create a separate dirty checking equals method to get around the binding source calling equals. I guess I do not understand why setting the data source to a binding source would call equals at all. To make the matter worse, if you are manipulating the binding source, the equals method will be invoked again.