Wednesday, April 29, 2009

Demystifying IComparer in C#

IComparerI was looking through the analytics on my blog and I noticed a trend. I get a ton of searches for things related to the IComparer in C# article I wrote a few months back. I can only imagine that the reason people are searching for information on the IComparer so frequently is because the IComparer is a little mysterious to most people. It's pretty uncommon that you need to use one (let alone write it).

In truth, the IComparer Documentation isn't really all that helpful. Most examples of an IComparer that I found when I was working on my best fit select and my quickselect articles simply use x.CompareTo(y) or a built in IComparer.

Sure, that works, but I think people are hitting my blog trying to uncover the inner workings of the actual comparison.

Well, here it is. IComparer has one member method called Compare(object x, object y) (the generic version is strongly typed) which returns an integer. If the value of x is less than y, the result is less than 0. If the value of x is greater than y, the result is greater than zero. If the two values are equal, the result is 0.

Now, in most implementations I've seen, the results are nominal data (i.e., one trit valued -1, 0, or 1); however, I don't really see any reason you can't return ordinal, interval, or ratio data as well (although, returning ordinal or interval data would be relatively difficult as it requires knowledge of the rest of the collection). If that didn't make sense, take a look at levels of measurement. If you'd like me to elaborate on levels of measurement, leave a comment and I'll write an entire post about it.

Using my fictional class Person with the Age property, a common AgeComparer class would look something like this:
public class AgeComparer : IComparer<Person>
{
public virtual int Compare(Person x, Person y)
{
if (x.Age == y.Age) return 0;
return (x.Age > y.Age) ? 1 : -1;
}
}
While that's useful for sorting, it doesn't really give you any information about the magnitude of the difference between the two objects and may as well just be called WhichOneIsGreater(object x, object y). If you were using the comparer to graph items in a list, this would be relatively useless. Also, it's an entire line of code more than you need, so I'm proposing a solution like the following:
public class AgeComparer : IComparer<Person>
{
public virtual int Compare(Person x, Person y)
{
return x.Age - y.Age;
}
}
One caveat is that some people use IComparers expecting 1, 0, or -1 (i.e., if (IComparer.Compare(x, y) == 1) {...}). Certainly, using a non-nominal result would break their code, but IMO had they read the documentation on IComparer, they'd have seen nothing about 1, 0, and -1 as the return value.

I challenge you to think of other comparisons you could make between objects and post them in the comments of this blog post. In the meantime, I'm going to start an entire blog section on IComparer and we'll see what we can come up with.

No comments:

Post a Comment