Share via


Comparator

I recently wrote a generic PriorityQueue class which can treat any type of object as the priority (not just integers).  Specifically, the class looked like this:

 public class PriorityQueue<T, TPriority>
{
    public PriorityQueue()
        : this(false)
    {
        ...
    }

    public PriorityQueue(bool invert)
        : this(Comparer<TPriority>.Default)
    {
        ...
    }

    public PriorityQueue(IComparer<TPriority> comparer)
    {
        ...
    }

    public void Enqueue(T item, TPriority priority)
    {
        ...
    }

    public KeyValuePair<T, TPriority> Dequeue()
    {
        ...
    }

    ...
}

Since it allowed any type of object as the priority object, it obviously can't use the standard numerical < and > operators to compare them.  Many classes in the .NET framework (such as List<T>) have this same pattern, so the framework provides an IComparer<T> interface and a Comparer<T> base class to support comparison of arbitrary types.  The interface only has one method you have to implement: int Compare(T x, T y).

Every time I implement IComparer I think it's silly that I have to create a new class (which usually also means a new file) that just defines one method.  In fact, the int Compare method is usually just a one-liner like String.Compare(x.SomeProperty, y.SomeProperty) since all I want to do is sort items based on SomeProperty.  Why do I have to create a whole new class (and whole new file) just to specify String.Compare(x.SomeProperty, y.SomeProperty)?

Obviously someone in the .NET framework team felt the same way, so they introduced a Comparison<T> delegate and added overloads to Array.Sort<T> and List<T>.Sort which accept this delegate instead of requiring an implementation of the IComparer interface.  Unfortunately they only added overloads for those two methods; there are still over twenty other methods in the .NET framework that require the IComparer<T> interface, including LINQ's IEnumerable<T> extension methods.

In order to use the single-delegate idea in all of the places that still require the interface, I created a new class called Comparator<T>:

 public class Comparator<T> : Comparer<T>
{
    private readonly Comparison<T> comparison;

    public Comparator(Comparison<T> comparison)
    {
        this.comparison = comparison;
    }

    public override int Compare(T x, T y)
    {
        return this.comparison(x, y);
    }
}

[Comments and argument checking omitted for brevity.]

This is probably the simplest class in my library.  It just takes a Comparison<T> delegate in the constructor and implements Comparer<T> (which also implements IComparer and IComparer<T>) by simply calling the comparison function.  This tiny little class allows you to use all of the IComparer-based methods and constructors (such as new SortedList(comparer), new SortedDictionary(comparer), IEnumerable<T>.OrderBy(), etc) without having to create a new class every time.

Comparator.cs