.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
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.
Page 1 of 2
