A Generic Comparison Class for Collection Items
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:
EDIT (08/26/2010) ------------- Fixed some non-escaped <> brackets.
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.