In Part 1 we looked at how to sort an array consisting of elements
that implemented IComparable. This approach works great if you have an array of, say,
primitive types, but what if you have an array of more complex objects that do not implement
IComparable, like FileInfo instances? As we saw in Part 1, the solution is
to create a custom class that implements IComparer, thusly providing the functionality
needed to compare items of the complex object's type. In this part we'll build this class to compare
two FileInfo objects.
Building a Class That Implements IComparer
The IComparer interface defines one method: Compare(obj1, obj2),
where both obj1 and obj2 are objects. The method must return an Integer identifying
if obj1 is less than, equal to, or greater than obj2. (As with the CompareTo()
method of the IComparable interface, a return value less than zero indicates obj1 is
less than obj2, a value of zero indicates that they are equal, and a positive value indicates that
obj1 is greater than obj2.)
So, any class that implements IComparer must also implement this Compare()
method. Recall that the FileInfo class has a variety of properties that we might want
to sort by, such as the file name, the file's size, the last written date, and others. Therefore,
it would be wise to allow for our comparer class to sort by any of these properties. To make this
a reality, let's start by creating an enumeration of all of the possible ways we want to allow the
user to sort the data. This can be accomplished by adding the following code to your ASP.NET Web
page's code-behind class file (outside of the class):
Public Enum CompareByOptions
FileName
LastWriteTime
Length
End Enum
Here we are have an enumeration with three members. Now, let's look at the code for the class to compare
FileInfo objects. Let's name this class CompareFileInfoEntries. Add this
class's code to the code-behind ASP.NET page as well, just like you did for the enumeration:
Public Class CompareFileInfoEntries
Implements IComparer
Private compareBy As CompareByOptions = CompareByOptions.FileName
Public Sub New(ByVal cBy As CompareByOptions)
compareBy = cBy
End Sub
Public Overridable Overloads Function Compare(ByVal file1 As Object, _
ByVal file2 As Object) As Integer Implements IComparer.Compare
'Convert file1 and file2 to FileInfo entries
Dim f1 As FileInfo = CType(file1, FileInfo)
Dim f2 As FileInfo = CType(file2, FileInfo)
'Compare the file names
Select Case compareBy
Case CompareByOptions.FileName
Return String.Compare(f1.Name, f2.Name)
Case CompareByOptions.LastWriteTime
Return DateTime.Compare(f1.LastWriteTime, f2.LastWriteTime)
Case CompareByOptions.Length
Return f1.Length - f2.Length
End Select
End Function
End Class
Note that this class implements the IComparer interface. In VB.NET this is accomplished
using the Implements keyword as shown above. In C#, you simply list the implemented interface(s)
after the class's name, like: class CompareFileInfoEntries : IComparer. This class contains
a single private member variable, compareBy, which specifies how the comparison should
be performed. The class's constructor takes in a CompareByOptions enumeration value,
specifying how the comparison should be made.
In the Compare() method, the first thing that we do is cast the two input objects from
objects to FileInfos. Next, we determine how we should be comparing the two FileInfo
objects. If the compareBy member variable is set to CompareByOptions.FileName,
we simply use the String class's Compare method to compare the Name of the
two FileInfo objects. If its set to CompareByOptions.LastWriteTime, we use
the DateTime class's Compare method to compare the LastWriteTime properties.
Finally, if the compareBy member variable is set to CompareByOptions.Length,
we return the difference of the two FileInfo's Length properties, since this
will return a negative number if the first FileInfo's Length is less than the
second's, 0 if they are equal, and a positive number if the first FileInfo's is greater
than the second's.
Putting It All Together
Now that we have the CompareFileInfoEntries we can sort an array of FileInfo
objects using the following syntax: Array.Sort(ArrayName, New CompareFileInfoEntries(HowToSort)),
where HowToSort is a member from the CompareByOptions enumeration. For example,
to sort the array by the FileInfos' Length properties, we'd use:
Array.Sort(ArrayName, New CompareFileInfoEntries(CompareByOptions.Length)).
We now want to convert our previous live demo into one that
allows for sorting. To accomplish this we first need to configure the DataGrid for sorting, which
entails setting the AllowSorting property to True and creating an event handler for the
DataGrid's SortCommand event. In the SortCommand event, then, we need to
determine what property the user wants to sort the DataGrid by, and then use the appropriate
syntax for the Array.Sort() method. The complete code can be seen below; there's also
an accompanying live demo.
Imports System.Collections
Imports System.IO
Public Class ListArticleSortable
Inherits System.Web.UI.Page
Protected WithEvents articleList As System.Web.UI.WebControls.DataGrid
#Region " Web Form Designer Generated Code "
'This call is required by the Web Form Designer.
<System.Diagnostics.DebuggerStepThrough()> _
Private Sub InitializeComponent()
End Sub
Private Sub Page_Init(ByVal sender As System.Object, _
ByVal e As System.EventArgs) _
Handles MyBase.Init
'CODEGEN: This method call is required by the Web Form Designer
'Do not modify it using the code editor.
InitializeComponent()
End Sub
#End Region
Private Sub Page_Load(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles MyBase.Load
If Not Page.IsPostBack Then
BindData(CompareByOptions.FileName)
End If
End Sub
Private Sub BindData(ByVal compareMethod As CompareByOptions)
Dim dirInfo As New DirectoryInfo(Server.MapPath(""))
Dim fileInfoArray() As FileInfo = dirInfo.GetFiles("*.aspx")
Array.Sort(fileInfoArray, New CompareFileInfoEntries(compareMethod))
articleList.DataSource = fileInfoArray
articleList.DataBind()
End Sub
Public Sub SortDisplay(ByVal sender As Object, ByVal e As _
DataGridSortCommandEventArgs)
Select Case e.SortExpression
Case "FileName"
BindData(CompareByOptions.FileName)
Case "LastWriteTime"
BindData(CompareByOptions.LastWriteTime)
Case "Length"
BindData(CompareByOptions.Length)
End Select
End Sub
End Class
Public Enum CompareByOptions
FileName
LastWriteTime
Length
End Enum
Public Class CompareFileInfoEntries
Implements IComparer
Private compareBy As CompareByOptions = CompareByOptions.FileName
Public Sub New(ByVal cBy As CompareByOptions)
compareBy = cBy
End Sub
Public Overridable Overloads Function Compare(ByVal file1 As _
Object, ByVal file2 As Object) _
As Integer Implements IComparer.Compare
'Convert file1 and file2 to FileInfo entries
Dim f1 As FileInfo = CType(file1, FileInfo)
Dim f2 As FileInfo = CType(file2, FileInfo)
'Compare the file names
Select Case compareBy
Case CompareByOptions.FileName
Return String.Compare(f1.Name, f2.Name)
Case CompareByOptions.LastWriteTime
Return DateTime.Compare(f1.LastWriteTime, f2.LastWriteTime)
Case CompareByOptions.Length
Return f1.Length - f2.Length
End Select
End Function
End Class
Conclusion
In this article we examined how to sort arrays. If the array's elements are of a type that implements
the IComparable interface, then sorting the arrays is as simple as calling
Array.Sort(ArrayName). For more complicated objects that do not implement the
IComparable interface, we can create a class designed specifically to compare to
of these objects. This class needs to implement the IComparer interface, thereby providing
a Compare() method. With such a class, we can use the Array.Sort() method to
sort an array by passing in both the array to sort and an instance of the compare class.
After presenting the basics of sorting .NET arrays, we then turned to a real-world problem: sorting
an array of FileInfo objects based on one of a variety of properties. Specifically, we
saw how to "upgrade" an earlier demo that merely listed the files in a directory in a DataGrid, to one
that listed the files in a directory in a sortable DataGrid.