I've labeled this as a level 100 topic because I feel that every .Net developer
should know the basics of sorting. With the base class library that we have to
work with, we don't have to worry about sorting algorithms or speeding up our
sorts. Folks smarter than I have implemented the fast sorting that we have in
.Net.
Here, I'll cover the basics of how
to sort your own domain object as well as how to sort any other object (even
ones you didn't create).
Let's consider
the following domain object, Person:
11 public class Person
12 {
13 private string _firstName;
14 private string
_lastName;
15
16 public Person(string firstName,
string
lastName)
17
{
18
_firstName = firstName;
19 _lastName =
lastName;
20
}
21
22 public string
FirstName
23
{
24
get { return _firstName;
}
25
}
26
27 public string
LastName
28
{
29
get { return _lastName;
}
30
}
31 }
In our application, we may want to display
FirstName LastName or LastName, FirstName. Also, we may want ascending or
descending sorts. If you are using a database, you may be tempted to let the
database order the records for you, and that's fine if you never need to change
the sort inside the application. In object-oriented systems, however, a lot of
code never talks to the database, so we need to be able to sort. Now, suppose I
wanted the following code to work:
[Test]
public void ShouldSortPersonsAscending()
{
Person[] persons =
new Person[]{new Person("Homer", "Simpson"), new Person("Bart", "Simpson"), new Person("Jerry", "Seinfeld")};
Array.Sort(persons);
foreach(Person person in persons)
{
Console.WriteLine(person.FullName);
}
}
This code won't work right now because
Array.Sort( ) counts on the elements in the array implementing the IComparable
interface. This is a key interface in .net for sorting. By adding an
implementation of that method to my Person class, I get sorting for free. What
I am going for here is a sort by last name and then first name:
public class Person : IComparable
{
private string _firstName;
private string
_lastName;
public Person(string firstName,
string
lastName)
{
_firstName
= firstName;
_lastName = lastName;
}
public string FirstName
{
get { return _firstName;
}
}
public string
LastName
{
get { return _lastName;
}
}
public string
FullName
{
get { return string.Format("{0} {1}",
FirstName, LastName); }
}
public int CompareTo(object obj)
{
Person person = (Person)obj;
int compareResult =
LastName.CompareTo(person.LastName);
if(compareResult == 0)
{
compareResult
= FirstName.CompareTo(person.FirstName);
}
return compareResult;
}
Notice how
I just had to implement a single method comparing first on last name and then on
first name. Here is my output:
------ Test started: Assembly: BlogScratchPad.dll ------
Jerry Seinfeld
Bart Simpson
Homer Simpson
1 passed, 0 failed, 0 skipped, took 0.41 seconds.
That's the bread and butter sorting case,
and that gives my Person object a default sort order. Anyone working with
Person(s) can now sort by last name, first name in ascending order. But what if
I wanted to sort in descending order? There is another key interface in .Net
called IComparer. This interface enables you to create a class that defines the
sort order for any class. Consider the following tests. I would like my
Person(s) to be listed in descending alphabetical order (reverse of above).
Here, I choose to implement the generic form to avoid some casting:
IComparer<T>.
[Test]
public void
ShouldSortPersonsDescending()
{
Person[] persons = new Person[] { new Person("Homer", "Simpson"), new Person("Bart", "Simpson"), new Person("Jerry", "Seinfeld")
};
Array.Sort(persons,
new ByLastNameFirstNameDescendingComparer());
foreach (Person person in persons)
{
Console.WriteLine(person.FullName);
}
}
Notice how this test looks very similar to
the previous one; however, I've added my new comparer class to instruct
Array.Sort( ) how to sort the array.
public class ByLastNameFirstNameDescendingComparer :
IComparer<Person>
{
public int Compare(Person x, Person y)
{
int compareResult =
y.LastName.CompareTo(x.LastName);
if (compareResult == 0)
{
compareResult
= y.FirstName.CompareTo(x.FirstName);
}
return compareResult;
}
}
Notice how I just compared y to x instead of
x to y. This gets me the descending order that I want. Here is the result of
running the test:
------ Test started: Assembly: BlogScratchPad.dll ------
Homer Simpson
Bart Simpson
Jerry Seinfeld
1 passed, 0 failed, 0 skipped, took 0.41 seconds.
Notice how it is exactly the
reverse of the previous result – just what I wanted.
Array.Sort(Array, IComparer) works to sort an array of any object under the
sun. My team's Visual Studio solution has accumulated a healthy number of
IComparer classes. This allows our application to sort objects however needed,
whenever needed.