.NET Generics - Sorting List By Class Properties

By Robbe D. Morris

Printer Friendly Version

Robbe Morris
Robbe & Melisa Morris
  Download C# Source Code
Like many .NET Developers, I'm just now jumping into the brave new world of generics  I won't regurgitate all of the basics concerning generics or show one more example of how to use it with strings or ints.  Instead, I'll show you a quick code sample of how you can wire up your own class property sorting via generic Lists.
Here's the real world scenario I have along with my solution.  We have 50-60 data classes that have no methods or events just properties.  Each of these data classes has differing properties.  Thus, a single, complex interface really isn't suitable.  That said, virtually all of the 50-60 data classes have at least a couple of "like" properties.  In particular, a flag for whether the record has changed (IsDirty), a flag whether the record has been flagged for deletion (IsFlaggedForDelete), a sort order (SortOrder), and a string for the text description (Description).  The business and user interface rules of the application make heavy use of generic lists.  In both areas, we also need to sort and reverse sort the entire list or a subset of that list by either its description or its sort order.
The code sample below creates a base class containing the four "like" properties from above for use when inherited by our primary data classes called StandardProperties.  The sample contains two such classes:  Section, SectionChild.  There is also a StandardPropertiesComparer class which provides a static reference to our standard sorting capabilities applied to any class that inherits StandardProperties.  I've also included a SectionComparer as a means to support class specific property sorting needs as well.
Using .NET Generics via the code technique below, I can automatically incorporate standard sorting capabilities for all of my data classes as well as easily add on custom property sorting for those classes that need it.  You'll also want to take notice of how I implement generic methods to write "like" properties to the console screen even though the classes passed in were different from each other.
The core of how this is able to run wildly different class interfaces through the same method parameters is that since the data classes inherit the base class of StandardProperties, .NET is able to restrict itself to only seeing those properties of the base class while still working with the whole class.
Let's check out the code below:
 


Sample Console Application
using System;
using System.Diagnostics;
using System.Collections.Generic;


namespace NullSkull
{
 
   class EntryPoint
   {

     [STAThread]
     static void Main(string[] args)
     {
            
       EntryPoint tester = new EntryPoint();

       try
       {
      
          tester.Test1();
            
       }
       catch (Exception e) 
       {
         Debug.WriteLine(e.Message);
       }
       finally
       {
          Console.ReadLine(); 
       }
     }
 
     private void Test1()
     {

       Section section = null;
       SectionChild sectionChild = null;
       List<Section> sections = null;
       List<SectionChild> sectionChildren = null;
       int testRecordCount = 10;

       try
       {

          sections = new List<Section>();
          sectionChildren = new List<SectionChild>();

          // Add some test data

          for (int i = testRecordCount; i > -1; i--)
          {
            section = new Section();
            section.SectionID = i;
            section.SortOrder = i;
            section.Description = "Section " + i.ToString();
            section.SectionSubName = "My Sub Name " + i.ToString(); 
            sections.Add(section);
          }

          for (int i = testRecordCount; i > -1; i--)
          {
             sectionChild = new SectionChild();
             sectionChild.GridSectionID = i.ToString();
             sectionChild.SortOrder = i;
             sectionChild.Description = "SectionChild " + i.ToString();
             sectionChildren.Add(sectionChild);
          }

          Console.WriteLine("");
          Console.WriteLine("Demonstrate sorting Section records");
          Console.WriteLine("");

          DemonstrateSort(sections);

          Console.WriteLine("");
          Console.WriteLine("Demonstrate sorting Section SubName Sort");
          Console.WriteLine("");

          sections.Sort(SectionComparer.CompareSectionSubName);

          for (int i = 0; i < sections.Count; i++)
          {
            Console.WriteLine(sections[i].SectionSubName);
          }

          Console.WriteLine("");

          sections.Reverse();

          for (int i = 0; i < sections.Count; i++)
          {
             Console.WriteLine(sections[i].SectionSubName);
          }
 
          Console.WriteLine("");
          Console.WriteLine("Demonstrate sorting SectionChild records");
          Console.WriteLine("");

          DemonstrateSort(sectionChildren);

       }
       catch (Exception) { throw; }

    }
 
    private void DemonstrateSort<T>(List<T> list) where T : StandardProperties
    {

       try
       {

          Console.WriteLine("");
          Console.WriteLine("Sorted by SortOrder ASC");
          Console.WriteLine("");

          // Use our static comparer for the SortOrder Property

          list.Sort(StandardPropertiesComparer.CompareSortOrder);

          WriteToConsoleScreen(list);

          Console.WriteLine("");
          Console.WriteLine("Sorted by SortOrder DESC");
          Console.WriteLine("");

          list.Reverse();

          WriteToConsoleScreen(list);

          Console.WriteLine("");
          Console.WriteLine("Sorted by SortOrder ASC");
          Console.WriteLine("");

          list.Sort(StandardPropertiesComparer.CompareSortOrder);

          WriteToConsoleScreen(list);

          Console.WriteLine("");
          Console.WriteLine("Sorted by Description ASC");
          Console.WriteLine("");

          // Let's try sorting strings like the Description property
         // with our static CompareDescription comparer.

          list.Sort(StandardPropertiesComparer.CompareDescription);

          WriteToConsoleScreen(list);
               

       }
       catch (Exception) { throw; }
    }
 
    private void WriteToConsoleScreen<T>(List<T> list) where T : StandardProperties
    {
           
       try
       {

          Console.WriteLine(" ");

          for (int i = 0; i < list.Count; i++)
          {
             Console.WriteLine(list[i].Description);
          }

       }
       catch (Exception) { throw; }
    }
    #endregion

 }

 // Create a sample Section class that inherits StandardProperties

 public class Section : StandardProperties
 {
   public int SectionID = 0;
   public int BlahForeignKeyID = 0;
   public int OtherBlahForeignKeyID = 0;
   public string SectionSubName = "";
 }
 
// Create a sample SectionChild class that inherits StandardProperties

 public class SectionChild : StandardProperties
 {
   public string GridSectionID = "";
   public int SomeForeignKeyID = 0;
   public int SomeOtherForeignKeyID = 0;
 }
 
   
// This class holds our standard properties and standard comparison
// methods required by the IComparable interface.

 public class StandardProperties : IComparable<StandardProperties>                         
 {

   private bool columnInfoIsDirty = false;
   public bool IsDirty
   {
      get
      {
         return columnInfoIsDirty;
      }
      set
      {
         columnInfoIsDirty = value;
      }
   }
 
   private bool columnInfoIsFlaggedForDelete = false;
   public bool IsFlaggedForDelete
   {
      get
      {
          return columnInfoIsFlaggedForDelete;
      }
      set
      {
         if (columnInfoIsFlaggedForDelete != value)
         {
           IsDirty = true;
           columnInfoIsFlaggedForDelete = value;
         }
      }
   }
 
    private int columnInfoSortOrder = 0;
    public int SortOrder
    {
       get
       {
         return columnInfoSortOrder;
       }
       set
       {
         if (columnInfoSortOrder != value)
         {
           IsDirty = true;
           columnInfoSortOrder = value;
         }
       }
    }
 
    private string columnInfoDescription = "";
    public string Description
    {
       get
       {
          return columnInfoDescription;
       }
       set
       {
          if (columnInfoDescription != value)
          {
            IsDirty = true;
            columnInfoDescription = value;
          }
       }
    }
 
    // Create overloads for Equals to support
   // our various needs for comparison by SortOrder
   // or Description properties.

     public bool Equals(StandardProperties comparedTo)
     {
       if (this.SortOrder == comparedTo.SortOrder)
       {
          return true;
       }
       return false;
     }
 
     public bool Equals(string comparedTo)
     {
       if (this.Description == comparedTo)
       {
         return true;
       }
       return false;
     }
 
    // Create overloads to support comparison methods
    // sorting on the SortOrder property or by the
    // a couple of strings.

     public int CompareTo(StandardProperties comparedTo)
     {
        return this.SortOrder.CompareTo(comparedTo.SortOrder);
     }

     public int CompareTo(string thisItem,string comparedTo)
     {
       return String.Compare(thisItem,comparedTo);
     }
 

 }

  // Create a static instance for our Standard Comparer.  We'll
  // be passing these as parameters to the .Sort method of our List.

 public class StandardPropertiesComparer  
 {

    public static int CompareSortOrder(StandardProperties thisItem,
                                        StandardProperties comparedTo)
     {
        return thisItem.CompareTo(comparedTo);
     }
 
    public static int CompareDescription(StandardProperties thisItem,
                                             StandardProperties comparedTo)
    {
        return thisItem.CompareTo(thisItem.Description,
                                      comparedTo.Description);
    }

 }

 // This is a custom class for demonstration purposes.
// I wanted to show how you could create your own comparer
// class where you could incorporate one or more comparison
// methods.

 public class SectionComparer : StandardPropertiesComparer
 {

    public static int CompareSectionSubName(Section thisItem,
                                            Section comparedTo)
    {
       return String.Compare(thisItem.SectionSubName,comparedTo.SectionSubName);
    }

 }

} 
 

Robbe has been a Microsoft MVP in C# since 2004.  He is also the co-founder of NullSkull.com which provides .NET articles, book reviews, software reviews, and software download and purchase advice.