Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

A Generic Comparison Class for Collection Items

4.83/5 (11 votes)
26 Aug 2010CPOL2 min read 1  
You probably have better things to do than writing tedious comparison methods.
I was looking around on the web for a method for sorting a list without having to write all the custom compare methods, and I came across this 2006 code on the by a guy named Dipend Lama on the c-sharpcorner website:

    Sorting Collection of Custom Type using Generic


After copying the code to my project, and seeing the results, I decided I occasionally needed to sort by more then just one property at a time. Unfortunately, this class didn't support that particular design consideration, so it was up to me to implement it.

My first task was to decide on a way to pass multiple (or even just one) properties on which to search. I settled on an array of strings because they're easy to instantiate in a method call. However, if just a single property was specified, I wanted to allow the programmer to do so without having to create a string array, so I kept the original constructor, and overloaded it with one that accepted a string array. The next problem was slightly tougher - wow was I going to sort the list with ALL of the properties?

My solution boiled down to a single basic programming practice - recursion. In all honesty, recursion is not all that common. I've written MILLIONS of lines of code in the last 30 years and have used recursion MAYBE half a dozen times.

It was actually very simple to do. I took the original Compare method:

public int Compare(T x, T y)
{
    PropertyInfo propertyInfo = typeof(T).GetProperty(sortColumn);
    IComparable obj1 = (IComparable)propertyInfo.GetValue(x, null);
    IComparable obj2 = (IComparable)propertyInfo.GetValue(y, null);
    if (sortingOrder == SortOrder.Ascending)
    {
        return (obj1.CompareTo(obj2));
    }
    else
    {
        return (obj2.CompareTo(obj1));
    }
}


and changed it to this:

public int Compare(T x, T y)
{
    return CompareProperty(0, x, y);
}

private int CompareProperty(int index, T x, T y)
{
    int result = 0;
    PropertyInfo propertyInfo = typeof(T).GetProperty(propertyArray[index]);
    IComparable  obj1        = (IComparable)propertyInfo.GetValue(x, null);
    IComparable  obj2        = (IComparable)propertyInfo.GetValue(y, null);
    if (sortingOrder == GenericSortOrder.Ascending)
    {
        result = (obj1.CompareTo(obj2));
    }
    else
    {
        result = (obj2.CompareTo(obj1));
    }
    if (result == 0 && index < propertyArray.Length - 1)
    {
        index++;
        result = CompareProperty(index, x, y);
    }
    return result;
}


The new method calls itself for each property in the string array, and once the result of the compare is not 0, I know it's done, and can back out of the stack. Here's the whole thing for easy cut/pasting into your own code.

public enum GenericSortOrder { Ascending, Descending };

public sealed class GenericComparer<T> : IComparer<T>
{
    private GenericSortOrder sortingOrder;
    private string[] propertyArray = null;

    //------------------------------------------------------------------
    public GenericSortOrder SortingOrder { get { return sortingOrder; } }

    //------------------------------------------------------------------
    public GenericComparer(string sortColumn, GenericSortOrder sortingOrder)
    {
        if (string.IsNullOrEmpty(sortColumn))
        {
            throw new Exception("The sortColumn parameter is null/empty");
        }
        this.sortingOrder = sortingOrder;
        this.propertyArray = new string[1] {sortColumn};
    }

    //------------------------------------------------------------------
    public GenericComparer(string[] properties, GenericSortOrder order)
    {
        if (properties == null || properties.Length < 1)
        {
            throw new Exception("Properties array cannot be null/empty.");
        }
        this.sortingOrder = order;
        this.propertyArray = properties;
    }

    //------------------------------------------------------------------
    public int Compare(T x, T y)
    {
        return CompareProperty(0, x, y);
    }

    //------------------------------------------------------------------
    private int CompareProperty(int index, T x, T y)
    {
        int result = 0;
        PropertyInfo propertyInfo = typeof(T).GetProperty(propertyArray[index]);
        IComparable  obj1        = (IComparable)propertyInfo.GetValue(x, null);
        IComparable  obj2        = (IComparable)propertyInfo.GetValue(y, null);
        if (sortingOrder == GenericSortOrder.Ascending)
        {
            result = (obj1.CompareTo(obj2));
        }
        else
        {
            result = (obj2.CompareTo(obj1));
        }
        if (result == 0 && index < propertyArray.Length - 1)
        {
            index++;
            result = CompareProperty(index, x, y);
        }
        return result;
    }
}


Usage looks something like this:

List<MyObject> list = new List<MyObject>();

list.Sort(new GenericCompare<MyObject>("singleProperty", GenericOrder.Descending);

list.Sort(new GenericCompare<MyObject>(new string[2] {"property1", "property2"}, 
          GenericSortOrder.Descending);


Care must be exercised that you don't sort on too many properties, or your code will throw a stack overflow exception.

There may be more elegant solutions out there, and if you know of one, by all means, post it as an alternative.

EDIT ===========================

And here's the compare method without recursion (many thanks to supercat9):

public int Compare(T x, T y)
{
    int          index  = 0;
    int          count  = propertyArray.Length;
    int          result = 0;
    PropertyInfo propertyInfo;
    IComparable  obj1;
    IComparable  obj2;
    do
    {
        propertyInfo = typeof(T).GetProperty(propertyArray[index]);
        obj1         = (IComparable)propertyInfo.GetValue(x, null);
        obj2         = (IComparable)propertyInfo.GetValue(y, null);
        result       = obj1.CompareTo(obj2);
        if (this.SortingOrder == GenericSortOrder.Descending)
        {
            result = -result;
        }
        index++;
    } while (result == 0 && index < count);
    return result;
}






EDIT (08/26/2010) -------------

Fixed some non-escaped <> brackets.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)