.NET Sorting: Compare Just About Any Property of Any Object

Archimedes once claimed that given a lever long enough, a fulcrum strong enough, and a place to stand, he could move the world. (Archimedes also reportedly ran through the streets of Athens naked shouting Eureka when he discovered displacement, but that's another story.) Advanced techniques such as interfaces, reflection, and generics may not enable you to move the world, but they are sufficient tools for doing a lot of work, which is what Archimedes meant.

One such powerful tool that the .NET Framework provides is the generic interface IComparer. Implement IComparer and provide the type of object to compare, and .NET supports sorting any kind of collection of objects. Implement IComparer and fold in some reflection, and your IComparer will compare just about any property of any object. Using reflection won't yield a lightening fast sort, but the tradeoff for speed is flexibility.

Implementing IComparer

IComparer takes a parameter type T and two arguments of type T. Your job is to implement the comparison behavior and return -1 for x < y, 0 for x = y, and >0 for x > y. You can slightly enhance this basic comparison behavior by passing in a property name to the constructor, passing in an enumerated value to indicate sort direction, and using reflection to obtain access to the property value.

Listing 1 shows how you can specify the value of enumerated fields to reverse the results of the sort behavior:

Listing 1: An Enumeration with Specific Values

Public Enum SortDirection
   Descending = -1
   Ascending  = 1
Enum

By multiplying the return value of a comparison by 1 or -1, you change the direction of the sort.

Listing 2 shows the implementation of the PropertyComparer class, which uses reflection to ensure that the field either implements IComparable or has a CompareTo method:

Listing 2: The PropertyComparer

Public Class PropertyComparer(Of T)
   Implements IComparer(Of T)
   Private FPropertyName As String = ""
   Private FDirection As SortDirection

   Public Sub New(ByVal propertyName As String)
      FPropertyName = propertyName
      FDirection    = SortDirection.Ascending
   End Sub

   Public Sub New(ByVal propertyName As String, _
      ByVal Direction As SortDirection)
      FPropertyName = propertyName
      FDirection    = Direction
   End Sub

   ' Try to sort based on type using CompareTo method
   ' Multiple by FDirection to alternate sort direction
   Public Function Compare(ByVal x As T, ByVal y As T) _
      As Integer Implements System.Collections.Generic. _
         IComparer(Of T).Compare

      Dim propertyX As PropertyInfo = _
         x.GetType().GetProperty(FPropertyName)
      Dim propertyY As PropertyInfo = _
         y.GetType().GetProperty(FPropertyName)
      Dim px As Object = propertyX.GetValue(x, Nothing)
      Dim py As Object = propertyY.GetValue(y, Nothing)

      If (TypeOf px Is Integer) Then
         Return Compare(Of Integer)(CType(px, Integer), _
            CType(py, Integer)) * FDirection
      End If

      If (TypeOf px Is Decimal) Then
         Return Compare(Of Decimal)(CType(px, Decimal), _
            CType(py, Decimal)) * FDirection
      End If

      If (TypeOf px Is DateTime) Then
         Return Compare(Of DateTime)(CType(px, DateTime), _
            CType(py, DateTime)) * FDirection
      End If

      If (TypeOf px Is Double) Then
         Return Compare(Of Double)(CType(px, Double), _
            CType(py, Double)) * FDirection
      End If

      If (TypeOf px Is String) Then
         Return Compare(Of String)(CType(px, String), _
            CType(py, String)) * FDirection
      End If

      If (TypeOf px Is Decimal) Then
         Return Compare(Of Decimal)(CType(px, Decimal), _
            CType(py, Decimal)) * FDirection
      End If

      Dim methodX As MethodInfo = _
         propertyX.GetType().GetMethod("CompareTo")
      If (methodX Is Nothing = False) Then
         Return CType(methodX.Invoke(px, New Object() {py}), _
            Integer) * FDirection
      Else
         Return 0
      End If
   End Function

   Private Function Compare(Of K As IComparable)(ByVal x As K, _
      ByVal y As K) As Integer
      Return x.CompareTo(y)
   End Function
End Class

The PropertyComparer class uses reflection to get the type information for the property passed into the PropertyComparer's constructor and attempts to call the property's CompareTo method (if it exists). If the property has a CompareTo method, the comparison will work. If no CompareTo method exists, the Compare method returns 0, which has a benign effect.

The key to understanding the PropertyComparer class lies in the boldfaced code of Listing 2. The first thing Compare does is get the PropertyInfo record for the x and y arguments. Next, it obtains the value of the property using the argument's x and y and the PropertyInfo record. Finally, it inspects the type of the property to determine how to call the Compare method implemented at the end of the class. The Compare method implemented has a where predicate that limits the types of the parameter K to those that implement IComparable. The reason for this is that IComparable types implement CompareTo.

.NET Sorting: Compare Just About Any Property of Any Object

Defining Something to Sort

You can sort just about anything. To make the demonstration reflect something you may be familiar with, Listing 3 contains an implementation of a simple Customer class:

Listing 3: A Simple Customer Class

Public Class Customer
   Private FCustomerNumber As Integer
   Private FName As String
   Public Sub New(ByVal customerNumber As Integer, _
      ByVal name As String)
         FCustomerNumber = customerNumber
         FName = name
   End Sub

   Public ReadOnly Property CustomerNumber() As Integer
      Get
         Return FCustomerNumber
      End Get
   End Property

   Public Property Name() As String
      Get
         Return FName
      End Get
      Set(ByVal value As String)
         FName = value
      End Set
   End Property
End Class

You can sort a list of Customer objects by the Name or CustomerNumber.

Invoking the Sort Behavior

Listing 4 demonstrates how to create a generic list of strongly typed Customer objects, add some Customers to the list, and sort the Customers by Name:

Listing 4: Code to Demonstrate the PropertyComparer

Imports System
Imports System.Collections.Generic
Imports System.Text
Imports System.Reflection

Module Module1
   Sub Main()
      Dim list As List(Of Customer) = New List(Of Customer)
      list.Add(New Customer("Paul"))
      list.Add(New Customer("Noah"))
      list.Add(New Customer("Alex"))
      list.Add(New Customer("Jim"))
      list.Sort(New PropertyComparer(Of Customer)("Name"))
      Dim o As Customer
      For Each o In list
         Console.WriteLine(o.Name)
      Next
      Console.ReadLine()
   End Sub
End Module

If you changed the construction of the PropertyComparer to initialize the PropertyComparer with the CustomerNumber property name, you would get a completely different sort result.

A General Technique for Sorting Objects

Advanced techniques are the lever, fulcrum, and place to stand that can help you move mountains. This example combined reflection, interfaces, and generics to create a general technique for sorting objects based on any field.

I use a variation of the PropertyComparer in production, and it's a nice addition to the sorting behavior in .NET.

Acknowledgements

Special thanks to my very smart friend Chris Chartrand in Ontario for coming up with the directional variation of my original PropertyComparer class.

About the Author

Paul Kimmel is the VB Today columnist for www.codeguru.com and has written several books on object-oriented programming and .NET. Check out his new book UML DeMystified from McGraw-Hill/Osborne. Paul is an architect for Tri-State Hospital Supply Corporation. You may contact him for technology questions at pkimmel@softconcepts.com.

If you are interested in joining or sponsoring a .NET Users Group, check out www.glugnet.org.



Comments

  • There are no comments yet. Be the first to comment!

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Live Webinar Tuesday, August 26, 2014 1:00 PM EDT Customers are more empowered and connected than ever, and the customer's journey has grown more complex. Their expectations are growing and trust is diminishing as they may interact with multiple brands through web, mobile and social channels. Considering 70% of the buying process in a complex sale is already complete before prospects are willing to engage with a live salesperson -- it's critical to understand your customers and anticipate their needs.* …

  • Java developers know that testing code changes can be a huge pain, and waiting for an application to redeploy after a code fix can take an eternity. Wouldn't it be great if you could see your code changes immediately, fine-tune, debug, explore and deploy code without waiting for ages? In this white paper, find out how that's possible with a Java plugin that drastically changes the way you develop, test and run Java applications. Discover the advantages of this plugin, and the changes you can expect to see …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds