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.