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#

9 comments »


 

9 comments

Comment from: Emilio Dabdoub [Visitor]
Excellent! just what I was looking for.
2005-05-16 @ 02:40
Comment from: matt [Visitor] Email
Joel,

will this sort a generic list of say Products where i sort on say product.ProductType.Name
(ProductType is a class and Name is a string)
??
ie. nested class hierarchy
2008-04-09 @ 03:52
Comment from: Joel Ross [Member] Email · http://www.rosscode.com
No, however we use one in the NuSoft Framework that does do exactly that.

If you download the samples at http://www.codeplex.com/NSFxSamples and look at the source for EntityComparper.cs (from soure: http://www.codeplex.com/NSFxSamples/SourceControl/FileView.aspx?itemId=212083&changeSetId=16449 ), You'll see how we are doing it now.
2008-04-09 @ 09:22
Comment from: Matt P [Visitor]
Thank you, this was extremely helpful.
2008-04-21 @ 13:25
Comment from: David Dou [Visitor] Email
Great! I am trying to write a Sorter with the same function for my generic List before i see this piece of code.
2008-10-08 @ 22:19
Comment from: sara [Visitor] Email
Great,
Try this too,
http://sarangasl.blogspot.com/2009/10/sort-object-arraylist-in-c.html
2009-10-14 @ 07:26
Comment from: Martijn [Visitor] Email
Thanks! That is a very useful class. :)
2009-10-24 @ 07:21
Comment from: karms [Visitor]
This doesn't work with nullables or null values. Do you have solution for that?
2010-05-21 @ 17:42
Comment from: kumar [Visitor]
Nice to see a code example and an attachment.
2010-06-16 @ 12:01

Leave a comment


Your email address will not be revealed on this site.

Your URL will be displayed.
(Line breaks become <br />)
(Name, email & website)
(Allow users to contact you through a message form (your email will not be revealed.)