Generic (Multiple) Sorting For Typed Collections

posted on 2004-11-13 at 23:10:47 by Joel Ross

I ran across this post earlier in the week, and it gave me the last peice I needed to complete my sorter class I wrote earlier this spring. Mine was missing the ability to sort on any type but strings. At the time, that was fine - all properties were treated as strings. But as I moved this to another project, we needed to sort on other types, like dates, numbers, etc. Using reflection, you can dynamically find the property, and then compare the values based on the type of the property.

So how is mine different than the one linked above? Mine supports subsorting. So I could sort orders by the date they were sold (descending), then by the sales person who sold them (ascending), and then by the total amount of the order (descending). Pretty powerful.

Anyway, here's the class:

     1: using System;
     2: using System.Collections;
     3: using System.Reflection;
     4:  
     5:  
     6: namespace Ross.Utilities {
     7:     /// <summary>
     8:     /// Class used to sort objects
     9:     /// </summary>
    10:     public class Comparer : IComparer {
    11:         private ArrayList _sortClasses;
    12:  
    13:         /// <summary>
    14:         /// The collection of sorting classes
    15:         /// </summary>
    16:         public ArrayList SortClasses {
    17:             get { return _sortClasses; }
    18:         }
    19:  
    20:         /// <summary>
    21:         /// Default Constructor
    22:         /// </summary>
    23:         public Comparer() {
    24:             _sortClasses = new ArrayList();
    25:         }
    26:  
    27:         /// <summary>
    28:         /// Constructor that takes a collection of sorting classes
    29:         /// </summary>
    30:         /// <param name="SortClasses">The prebuilt collection of sort information</param>
    31:         public Comparer(ArrayList SortClasses) {
    32:             _sortClasses = SortClasses;
    33:         }
    34:  
    35:         /// <summary>
    36:         /// Constructor that takes the information about one sort
    37:         /// </summary>
    38:         /// <param name="SortColumn">The column to sort on</param>
    39:         /// <param name="SortDirection">The direction to sort</param>
    40:         public Comparer(string SortColumn, SortDirection SortDirection) {
    41:             _sortClasses = new ArrayList();
    42:             _sortClasses.Add(new SortClass(SortColumn, SortDirection));
    43:         }
    44:         
    45:         /// <summary>
    46:         /// IComparer interface implementation to compare two objects
    47:         /// </summary>
    48:         /// <param name="x">Object 1</param>
    49:         /// <param name="y">Object 2</param>
    50:         /// <returns></returns>
    51:         public int Compare(object x, object y) {
    52:             if(SortClasses.Count == 0) {
    53:                 return 0;
    54:             }
    55:             return CheckSort(0, x, y);
    56:         }
    57:  
    58:         /// <summary>
    59:         /// Recursive function to do sorting
    60:         /// </summary>
    61:         /// <param name="SortLevel">The current level we are sorting at</param>
    62:         /// <param name="MyObject1">Object 1</param>
    63:         /// <param name="MyObject2">Object 2</param>
    64:         /// <returns></returns>
    65:         private int CheckSort(int SortLevel, object MyObject1, object MyObject2) {
    66:             int returnVal = 0;
    67:             
    68:             if(SortClasses.Count - 1 >= SortLevel) {
    69:                 object valueOf1 = MyObject1.GetType().GetProperty(((SortClass) SortClasses[SortLevel]).SortColumn).GetValue(MyObject1, null);
    70:                 object valueOf2 = MyObject2.GetType().GetProperty(((SortClass) SortClasses[SortLevel]).SortColumn).GetValue(MyObject2, null);
    71:  
    72:                 if(((SortClass) SortClasses[SortLevel]).SortDirection == SortDirection.Ascending) {
    73:                     returnVal = ((IComparable) valueOf1).CompareTo(valueOf2);
    74:                 } 
    75:                 else {
    76:                     returnVal = ((IComparable) valueOf2).CompareTo(valueOf1);
    77:                 }
    78:  
    79:                 if(returnVal == 0){
    80:                     returnVal = CheckSort(SortLevel + 1, MyObject1, MyObject2);
    81:                 }
    82:             }
    83:             return returnVal;
    84:         }
    85:     }
    86:  
    87:     /// <summary>
    88:     /// Enumeration to determine sorting direction
    89:     /// </summary>
    90:     public enum SortDirection {
    91:         /// <summary>Sort Ascending</summary>
    92:         Ascending = 1,
    93:  
    94:         /// <summary>Sort Descending</summary>
    95:         Descending = 2
    96:     }
    97:  
    98:     /// <summary>
    99:     /// Class used to hold sort information
   100:     /// </summary>
   101:     public class SortClass {
   102:         /// <summary>
   103:         /// Default constructor taking a column and a direction
   104:         /// </summary>
   105:         /// <param name="SortColumn">The column to sort on</param>
   106:         /// <param name="SortDirection">The direction to sort.</param>
   107:         public SortClass(string SortColumn, SortDirection SortDirection) {
   108:             this.SortColumn = SortColumn;
   109:             this.SortDirection = SortDirection;
   110:         }
   111:  
   112:         private string    _sortColumn;
   113:         
   114:         /// <summary>
   115:         /// The column to sort on
   116:         /// </summary>
   117:         public string SortColumn {
   118:             get { return _sortColumn; }
   119:             set { _sortColumn = value; }
   120:         }
   121:         
   122:         private SortDirection _sortDirection;
   123:  
   124:         /// <summary>
   125:         /// The direction to sort
   126:         /// </summary>
   127:         public SortDirection SortDirection {
   128:             get { return _sortDirection; }
   129:             set { _sortDirection = value; }
   130:         }
   131:     }
   132: }

To use it, you add a SortClass to the Comparer. You can add as many as you want. A SortClass is made up of the property name and a direction. So, for example:

     1:     public MyCollectionClass SortMyClass(MyCollectionClass myClass){
     2:         Comparer comparer = new Comparer();
     3:         comparer.SortClasses.Add(new SortClass("MyProperty1", SortDirection.Ascending);
     4:         comparer.SortClasses.Add(new SortClass("MyProperty2", SortDirection.Descending);
     5:         myClass.Sort(comparer);
     6:         return myClass;
     7:     }

So after the sort, myClass would be sorted by MyProperty1 (ascending), then by MyProperty2 (descending).

Categories: C#