Showing posts with label arrays. Show all posts
Showing posts with label arrays. Show all posts

Thursday, July 2, 2009

Sorting Arrays and Collections (Part 3)

Part 1 here
Part 2 here

In part 1 of this series we looked at using the IComparable interface to provide sorting by allowing objects to compare themselves to each other. In part 2 we looked at using the IComparer interface to provide custom sorting through the external comparison of objects that may or may not be inherently comparable. In this third and final part we look at using the Comparison(T) delegate to provide similar functionality to the IComparer interface but without the need to define a whole class. The Comparison delegate allows us to use a method declared anywhere to perform the comparisons. Most often that would be in the same code file as we’re performing a one-off sort.

The signature of the Comparison delegate should put you in mind of the methods we’ve been using for comparisons already. It takes two arguments of type T and returns an Int32 value that indicates their relative order. That’s exactly the same as the IComparer.Compare method. Not surprisingly, the implementation of the method referred to by this delegate should be exactly the same as the implementation of an equivalent IComparer.Compare method.

Going back to our example from part 2, where we were able to sort a list of Person objects by both LastName and FirstName, we can basically extract the implementations of the CompareByFirstName and CompareByLastName methods. Instead of declaring them in a separate class that implements the IComparer interface we can declare them right there in the same class that contains the sorting code:

C#

private int ComparePeopleByFirstName(Person x, Person y)
{
    // Compare by FirstName by default.
    int result = x.FirstName.CompareTo(y.FirstName);
 
    // If FirstNames are the same...
    if (result == 0)
    {
        // ...compare by LastName.
        result = x.LastName.CompareTo(y.LastName);
    }
 
    return result;
}
 
private int ComparePeopleByLastName(Person x, Person y)
{
    // Compare by LastName by default.
    int result = x.LastName.CompareTo(y.LastName);
 
    // If LastNames are the same...
    if (result == 0)
    {
        // ...compare by FirstName.
        result = x.FirstName.CompareTo(y.FirstName);
    }
 
    return result;
}

VB

Private Function ComparePeopleByFirstName(ByVal x As Person, _
                                          ByVal y As Person) As Integer
    'Compare by FirstName by default.
    Dim result As Integer = x.FirstName.CompareTo(y.FirstName)
 
    'If FirstNames are the same...
    If result = 0 Then
        '...compare by LastName.
        result = x.LastName.CompareTo(y.LastName)
    End If
 
    Return result
End Function
 
Private Function ComparePeopleByLastName(ByVal x As Person, _
                                         ByVal y As Person) As Integer
    'Compare by LastName by default.
    Dim result As Integer = x.LastName.CompareTo(y.LastName)
 
    'If LastNames are the same...
    If result = 0 Then
        '...compare by FirstName.
        result = x.FirstName.CompareTo(y.FirstName)
    End If
 
    Return result
End Function

We can then create an instance of the Comparison delegate that references one or other of those methods to sort by the appropriate properties:

C#

List<Person> people = new List<Person>();
 
people.Add(new Person("Mary", "Smith"));
people.Add(new Person("John", "Williams"));
people.Add(new Person("John", "Smith"));
people.Add(new Person("Andrew", "Baxter"));
 
Console.WriteLine("Before sorting:");
 
foreach (Person person in people)
{
    Console.WriteLine(string.Format("{0}, {1}",
                                    person.LastName,
                                    person.FirstName));
}
 
people.Sort(new Comparison<Person>(ComparePeopleByLastName));
 
Console.WriteLine("After sorting by LastName:");
 
foreach (Person person in people)
{
    Console.WriteLine(string.Format("{0}, {1}",
                                    person.LastName,
                                    person.FirstName));
}
 
people.Sort(new Comparison<Person>(ComparePeopleByFirstName));
 
Console.WriteLine("After sorting by FirstName:");
 
foreach (Person person in people)
{
    Console.WriteLine(string.Format("{0} {1}",
                                    person.FirstName,
                                    person.LastName));
}

VB

Dim people As New List(Of Person)
 
people.Add(New Person("Mary", "Smith"))
people.Add(New Person("John", "Williams"))
people.Add(New Person("John", "Smith"))
people.Add(New Person("Andrew", "Baxter"))
 
Console.WriteLine("Before sorting:")
 
For Each person As Person In people
    Console.WriteLine(String.Format("{0}, {1}", _
                                    person.LastName, _
                                    person.FirstName))
Next
 
people.Sort(New Comparison(Of Person)(AddressOf ComparePeopleByLastName))
 
Console.WriteLine("After sorting by LastName:")
 
For Each person As Person In people
    Console.WriteLine(String.Format("{0}, {1}", _
                                    person.LastName, _
                                    person.FirstName))
Next
 
people.Sort(New Comparison(Of Person)(AddressOf ComparePeopleByFirstName))
 
Console.WriteLine("After sorting by FirstName:")
 
For Each person As Person In people
    Console.WriteLine(String.Format("{0} {1}", _
                                    person.FirstName, _
                                    person.LastName))
Next

Removing the need to declare a whole new class makes the Comparison delegate more convenient than the IComparer interface in many cases, but it can be more convenient still if we employ anonymous methods or lambda expressions.

C# 2.0 introduced the notion of anonymous methods. Anonymous methods can be used to initialise a delegate just as a named method can. If the code to perform our comparisons is relatively simple then using an anonymous method to create our Comparison delegate is simpler than writing a separate named method. Going back to our example from part 2, where we sorted strings by length instead of alphabetically, we could rewrite that code as follows:

C#

List<string> strings = new List<string>();
 
strings.Add("The longest of all");
strings.Add("Short");
strings.Add("Even longer");
strings.Add("Longer");
 
Console.WriteLine("Before sorting:");
 
foreach (string str in strings)
{
    Console.WriteLine(str);
}
 
strings.Sort(delegate(string x,
                      string y)
{
    return x.Length.CompareTo(y.Length);
});
 
Console.WriteLine("After sorting:");
 
foreach (string str in strings)
{
    Console.WriteLine(str);
}

Our anonymous method has the appropriate signature to initialise a Comparison delegate, i.e. it has two parameters of type string and a return type of int, so the C# compiler accepts it and invokes the appropriate overload of the Sort method.

VB 8 has no equivalent to C# anonymous methods but both VB 9 and C# 3.0 introduced lambda expressions as a supporting feature for LINQ. A lambda expression can be used to initialise a delegate in essentially the same way as an anonymous method:

C#

var strings = new List<string> {"The longest of all",
                                "Short",
                                "Even longer",
                                "Longer"};
 
Console.WriteLine("Before sorting:");
 
foreach (var str in strings)
{
    Console.WriteLine(str);
}
 
strings.Sort((x, y) => x.Length.CompareTo(y.Length));
 
Console.WriteLine("After sorting:");
 
foreach (var str in strings)
{
    Console.WriteLine(str);
}

VB

Dim strings As New List(Of String)
 
strings.Add("The longest of all")
strings.Add("Short")
strings.Add("Even longer")
strings.Add("Longer")
 
Console.WriteLine("Before sorting:")
 
For Each s In strings
    Console.WriteLine(s)
Next
 
strings.Sort(Function(x, y) x.Length.CompareTo(y.Length))
 
Console.WriteLine("After sorting:")
 
For Each s In strings
    Console.WriteLine(s)
Next

In each case the compiler infers the types of the lambda parameters based on the generic type of the list being sorted.

Just note that anonymous methods and lambdas make your code simpler if they themselves are simple but your code can quickly become difficult to read if you try to perform complex comparisons using an inline method.

So, let’s sum up the different ways we’ve discussed for sorting arrays and collections in the three parts of this series. First, we looked at performing automatic sorts on lists of objects that could compare themselves, thanks to their implementing the IComparable interface. Next, we looked at using the IComparer interface to create a class that could perform complex comparisons on objects that might not be inherently comparable. That class could also be reused in multiple code files or even multiple projects. Finally, we looked at using the generic Comparison delegate to invoke standard methods, anonymous methods and lambda expressions for simple custom sorts.

Happy sorting!

Wednesday, July 1, 2009

Sorting Arrays and Collections (Part 2)

Part 1 here

In the previous instalment we discussed how to use the IComparable interface to facilitate automatic sorting of arrays and collections. This time we will look at the IComparer interface and how it can be used to sort objects that may or may not be inherently comparable.

Like IComparable,the IComparer interface comes in both standard and generic flavours. Normally you would implement the generic version, although there may be instances where you need to implement the standard version. Sorting the items in a ListView is one such instance.

Again like IComparable, the IComparer interface declares only a single method. Where the IComparable.CompareTo method takes a single object and compares it to the current instance, the IComparer.Compare method takes two objects and compares them to each other. As an example, let's consider the case where you have a collection of strings that you want to sort by length instead of alphabetically. In this case we cannot rely on the IComparable implementation of the String class itself so we must define our own comparison, which we can do by implementing the IComparer interface:

C#

public class StringLengthComparer : IComparer<string>
{
    public int Compare(string x, string y)
     {
         return x.Length.CompareTo(y.Length);
     }
}

VB

Public Class StringLengthComparer
    Implements IComparer(Of String)
 
    Public Function Compare(ByVal x As String, _
                            ByVal y As String) As Integer _
    Implements IComparer(Of String).Compare
        Return x.Length.CompareTo(y.Length)
    End Function
 
End Class

Our StringLengthComparer.Compare method will take two Strings and return a result that indicates their relative order based on their Lengths. Notice that we are still making use of the IComparable.CompareTo method, which we know is a good convention to follow. In this case we are comparing the two Length properties, which are type Int32. The Int32 structure implements the IComparable interface so we should make use of it.

We can now perform a custom sort of a list of strings like so:

C#

List<string> strings = new List<string>();
 
strings.Add("The longest of all");
strings.Add("Short");
strings.Add("Even longer");
strings.Add("Longer");
 
Console.WriteLine("Before sorting:");
 
foreach (string str in strings)
{
     Console.WriteLine(str);
}
 
strings.Sort(new StringLengthComparer());
 
Console.WriteLine("After sorting:");
 
foreach (string str in strings)
{
     Console.WriteLine(str);
}

VB

Dim strings As New List(Of String)
 
strings.Add("The longest of all")
strings.Add("Short")
strings.Add("Even longer")
strings.Add("Longer")
 
Console.WriteLine("Before sorting:")
 
For Each str As String In strings
    Console.WriteLine(str)
Next
 
strings.Sort(New StringLengthComparer)
 
Console.WriteLine("After sorting:")
 
For Each str As String In strings
    Console.WriteLine(str)
Next

In this case we pass an instance of our StringLengthComparer class as an argument when we call Sort and all comparisons will be done using the Compare method of our class instead of the CompareTo methods of the objects in the list. Sorting arrays is much the same except, again, the Array.Sort method is static/Shared:

C#

string[] strings = {"The longest of all",
                    "Short",
                    "Even longer",
                    "Longer"};
 
Console.WriteLine("Before sorting:");
 
foreach (string str in strings)
{
    Console.WriteLine(str);
}
 
Array.Sort(strings, new StringLengthComparer());
 
Console.WriteLine("After sorting:");
 
foreach (string str in strings)
{
    Console.WriteLine(str);
}

VB

Dim strings As String() = {"The longest of all", _
                           "Short", _
                           "Even longer", _
                           "Longer"}
 
Console.WriteLine("Before sorting:")
 
For Each str As String In strings
    Console.WriteLine(str)
Next
 
Array.Sort(strings, New StringLengthComparer)
 
Console.WriteLine("After sorting:")
 
For Each str As String In strings
    Console.WriteLine(str)
Next

Now, our StringLengthComparer class is relatively simple. It only knows how to compare strings in one way. What if we want to be able compare objects in various ways depending on the circumstances? We can certainly do that with a class that implements IComparer. There’s no limit to the complexity of the class, as long as it provides the functionality defined by the interface. For example, let’s consider our Person class from the previous instalment:

C#

public class Person : IComparable, IComparable<Person>
{
     private string _lastName;
     private string _firstName;
 
     public string LastName
     {
        get { return this._lastName; }
         set { this._lastName = value; }
     }
 
     public string FirstName
     {
         get { return this._firstName; }
         set { this._firstName = value; }
     }
 
     public Person(string firstName, string lastName)
     {
         this._firstName = firstName;
         this._lastName = lastName;
     }
 
     public int CompareTo(object obj)
     {
         return this.CompareTo((Person) obj);
     }
 
     public int CompareTo(Person other)
     {
        // Compare by LastName by default.
        int result = this.LastName.CompareTo(other.LastName);
 
         // If LastNames are the same...
         if (result == 0)
        {
            // ...compare by FirstName.
            result = this.FirstName.CompareTo(this.FirstName);
        }
 
        return result;
     }
}

VB

Public Class Person
    Implements IComparable, IComparable(Of Person)
 
    Private _lastName As String
    Private _firstName As String
 
    Public Property FirstName() As String
        Get
            Return Me._firstName
        End Get
        Set(ByVal value As String)
            Me._firstName = value
        End Set
    End Property
 
    Public Property LastName() As String
        Get
            Return Me._lastName
        End Get
        Set(ByVal value As String)
            Me._lastName = value
        End Set
    End Property
 
    Public Sub New(ByVal firstName As String, _
                   ByVal lastName As String)
        Me._firstName = firstName
        Me._lastName = lastName
    End Sub
 
    Public Function CompareTo(ByVal obj As Object) As Integer _
    Implements System.IComparable.CompareTo
        Return Me.CompareTo(DirectCast(obj, Person))
    End Function
 
    Public Function CompareTo(ByVal other As Person) As Integer _
    Implements System.IComparable(Of Person).CompareTo
        'Compare by LastName by default.
        Dim result As Integer = Me.LastName.CompareTo(other.LastName)
 
        'If LastNames are the same...
        If result = 0 Then
            '...compare by FirstName.
            result = Me.FirstName.CompareTo(other.FirstName)
        End If
 
        Return result
    End Function
 
End Class

What if we want to be able to sort a list of Person objects by either FirstName or LastName? The Person class already implements the IComparable interface, which implementation compares by LastName then FirstName. As such, we could just provide an implementation of IComparer that compares by FirstName then LastName. That way we could either accept the default comparison or use our IComparer implementation, depending on the circumstances. For consistency though, a better idea might be to provide an IComparer implementation that can compare in either way and then use that all the time:

C#

public class PersonComparer : IComparer<Person>
{
    public enum ComparisonProperty
    {
        FirstName,
        LastName
    }
 
    private readonly ComparisonProperty _comparisonProperty;
 
    public PersonComparer(ComparisonProperty comparisonProperty)
    {
        this._comparisonProperty = comparisonProperty;
    }
 
    public int Compare(Person x, Person y)
    {
        int result = 0;
 
        switch (this._comparisonProperty)
        {
            case ComparisonProperty.FirstName:
                result = this.CompareByFirstName(x, y);
                break;
            case ComparisonProperty.LastName:
                result = this.CompareByLastName(x, y);
                break;
        }
 
        return result;
    }
 
    private int CompareByFirstName(Person x, Person y)
    {
        // Compare by FirstName by default.
        int result = x.FirstName.CompareTo(y.FirstName);
 
        // If FirstNames are the same...
        if (result == 0)
        {
            // ...compare by LastName.
            result = x.LastName.CompareTo(y.LastName);
        }
 
        return result;
    }
 
    private int CompareByLastName(Person x, Person y)
    {
        // Compare by LastName by default.
        int result = x.LastName.CompareTo(y.LastName);
 
        // If LastNames are the same...
        if (result == 0)
        {
            // ...compare by FirstName.
            result = x.FirstName.CompareTo(y.FirstName);
        }
 
        return result;
    }
}

VB

Public Class PersonComparer
    Implements IComparer(Of Person)
 
    Public Enum ComparisonProperty
        FirstName
        LastName
    End Enum
 
    Private ReadOnly _comparisonProperty As ComparisonProperty
 
    Public Sub New(ByVal comparisonProperty As ComparisonProperty)
        Me._comparisonProperty = comparisonProperty
    End Sub
 
    Public Function Compare(ByVal x As Person, _
                            ByVal y As Person) As Integer _
    Implements IComparer(Of Person).Compare
        Dim result As Integer
 
        Select Case Me._comparisonProperty
            Case ComparisonProperty.FirstName
                result = Me.CompareByFirstName(x, y)
            Case ComparisonProperty.LastName
                result = Me.CompareByLastName(x, y)
        End Select
 
        Return result
    End Function
 
    Private Function CompareByFirstName(ByVal x As Person, _
                                        ByVal y As Person) As Integer
        'Compare by FirstName by default.
        Dim result As Integer = x.FirstName.CompareTo(y.FirstName)
 
        'If FirstNames are the same...
        If result = 0 Then
            '...compare by LastName.
            result = x.LastName.CompareTo(y.LastName)
        End If
 
        Return result
    End Function
 
    Private Function CompareByLastName(ByVal x As Person, _
                                       ByVal y As Person) As Integer
        'Compare by LastName by default.
        Dim result As Integer = x.LastName.CompareTo(y.LastName)
 
        'If LastNames are the same...
        If result = 0 Then
            '...compare by FirstName.
            result = x.FirstName.CompareTo(y.FirstName)
        End If
 
        Return result
    End Function
 
End Class

Note that the CompareByLastName method compares in exactly the same way as the Person.CompareTo method. As such, we could have made use of that existing functionality in our PersonComparer class:

C#

private int CompareByLastName(Person x, Person y)
{
    return x.CompareTo(y);
}

VB

Private Function CompareByLastName(ByVal x As Person, _
                                   ByVal y As Person) As Integer
    Return x.CompareTo(y)
End Function

This does save us duplicating some code but it also makes the implementation of the PersonComparer class more reliant on the implementation of the Person class. By implementing our CompareByLastName method exactly as we want it, we allow the IComparable implementation of the Person class to change without changing the behaviour of our PersonComparer class.

We can now put our PersonComparer class to work in sorting a list of Person objects by either LastName or by FirstName:

C#

List<Person> people = new List<Person>();
 
people.Add(new Person("Mary", "Smith"));
people.Add(new Person("John", "Williams"));
people.Add(new Person("John", "Smith"));
people.Add(new Person("Andrew", "Baxter"));
 
Console.WriteLine("Before sorting:");
 
foreach (Person person in people)
{
    Console.WriteLine(string.Format("{0}, {1}",
                                    person.LastName,
                                    person.FirstName));
}
 
people.Sort(new PersonComparer(PersonComparer.ComparisonProperty.LastName));
 
Console.WriteLine("After sorting by LastName:");
 
foreach (Person person in people)
{
    Console.WriteLine(string.Format("{0}, {1}",
                                    person.LastName,
                                    person.FirstName));
}
 
people.Sort(new PersonComparer(PersonComparer.ComparisonProperty.FirstName));
 
Console.WriteLine("After sorting by FirstName:");
 
foreach (Person person in people)
{
    Console.WriteLine(string.Format("{0} {1}",
                                    person.FirstName,
                                    person.LastName));
}

VB

Dim people As New List(Of Person)
 
people.Add(New Person("Mary", "Smith"))
people.Add(New Person("John", "Williams"))
people.Add(New Person("John", "Smith"))
people.Add(New Person("Andrew", "Baxter"))
 
Console.WriteLine("Before sorting:")
 
For Each person As Person In people
    Console.WriteLine(String.Format("{0}, {1}", _
                                    person.LastName, _
                                    person.FirstName))
Next
 
people.Sort(New PersonComparer(PersonComparer.ComparisonProperty.LastName))
 
Console.WriteLine("After sorting by LastName:")
 
For Each person As Person In people
    Console.WriteLine(String.Format("{0}, {1}", _
                                    person.LastName, _
                                    person.FirstName))
Next
 
people.Sort(New PersonComparer(PersonComparer.ComparisonProperty.FirstName))
 
Console.WriteLine("After sorting by FirstName:")
 
For Each person As Person In people
    Console.WriteLine(String.Format("{0} {1}", _
                                    person.FirstName, _
                                    person.LastName))
Next

As I said earlier, you can make your class as complex as you like, comparing objects in numerous different ways involving as many properties as you want.

Now, implementing the IComparer interface in a new class is a good option if you want to be able to compare instances of a type in multiple different ways and/or in multiple different places. If you only need to sort in one place, or at least only in one code file, then there is a slightly easier way. We’ll look at that in the next instalment.

Part 3 here

Tuesday, June 9, 2009

Sorting Arrays and Collections (Part 1)

The key to sorting a list of objects is the ability to compare any two of those objects. No matter the sorting algorithm you choose, to sort a list of objects you must be able to compare a pair of those objects and then be able to decide how to proceed based on the result. The first choice for comparing objects in the .NET Framework is the IComparable interface.

The IComparable interface and its generic counterpart define a single CompareTo method. This method compares the current instance to another instance of the same type and returns an Int32 value indicating their relative order. A result less than zero indicates that the current instance comes before the other value, while a result greater than zero indicates that the other value comes first. A zero result indicates that the two values are equivalent.

All the primitive .NET data types, including String, implement the IComparable interface, allowing arrays and collections of such values to be sorted automatically, e.g.

C#

List<int> numbers = new List<int>();
 
numbers.Add(7);
numbers.Add(2);
numbers.Add(5);
numbers.Add(3);
 
Console.WriteLine("Before sorting:");
 
foreach (int number in numbers)
{
    Console.WriteLine(number);
}
 
numbers.Sort();
 
Console.WriteLine("After sorting:");
 
foreach (int number in numbers)
{
    Console.WriteLine(number);
}

VB

Dim numbers As New List(Of Integer)
 
numbers.Add(7)
numbers.Add(2)
numbers.Add(5)
numbers.Add(3)
 
Console.WriteLine("Before sorting:")
 
For Each number As Integer In numbers
    Console.WriteLine(number)
Next
 
numbers.Sort()
 
Console.WriteLine("After sorting:")
 
For Each number As Integer In numbers
    Console.WriteLine(number)
Next

Sorting arrays is basically the same except that the Array.Sort method is a static/Shared method:

C#

int[] numbers = { 7, 2, 5, 3 };
 
Console.WriteLine("Before sorting:");
 
foreach (int number in numbers)
{
    Console.WriteLine(number);
}
 
Array.Sort(numbers);
 
Console.WriteLine("After sorting:");
 
foreach (int number in numbers)
{
    Console.WriteLine(number);
}

VB

Dim numbers As Integer() = {7, 2, 5, 3}
 
Console.WriteLine("Before sorting:")
 
For Each number As Integer In numbers
    Console.WriteLine(number)
Next
 
Array.Sort(numbers)
 
Console.WriteLine("After sorting:")
 
For Each number As Integer In numbers
    Console.WriteLine(number)
Next

The same principle holds for String, DateTime and any other type that implements the IComparable interface. As such, we can define our own types that implement IComparable and then they will be able to be automatically sorted too. For instance, let’s say that we have a Person class and we want them to be inherently sortable by LastName and FirstName. Such a class could look like this:

C#

public class Person : IComparable, IComparable<Person>
{
    private string _lastName;
    private string _firstName;
 
    public string LastName
    {
        get
        {
            return this._lastName;
        }
        set
        {
            this._lastName = value;
        }
    }
 
    public string FirstName
    {
        get
        {
            return this._firstName;
        }
        set
        {
            this._firstName = value;
        }
    }
 
    public Person(string firstName, string lastName)
    {
        this._firstName = firstName;
        this._lastName = lastName;
    }
 
    public int CompareTo(object obj)
    {
        return this.CompareTo((Person)obj);
    }
 
    public int CompareTo(Person other)
    {
        // Compare by LastName by default.
        int result = this.LastName.CompareTo(other.LastName);
 
        // If LastNames are the same...
        if (result == 0)
        {
            // ...compare by FirstName.
            result = this.FirstName.CompareTo(this.FirstName);
        }
 
        return result;
    }
}

VB

Public Class Person
    Implements IComparable, IComparable(Of Person)
 
    Private _lastName As String
    Private _firstName As String
 
    Public Property FirstName() As String
        Get
            Return Me._firstName
        End Get
        Set(ByVal value As String)
            Me._firstName = value
        End Set
    End Property
 
    Public Property LastName() As String
        Get
            Return Me._lastName
        End Get
        Set(ByVal value As String)
            Me._lastName = value
        End Set
    End Property
 
    Public Sub New(ByVal firstName As String, _
                   ByVal lastName As String)
        Me._firstName = firstName
        Me._lastName = lastName
    End Sub
 
    Public Function CompareTo(ByVal obj As Object) As Integer _
    Implements IComparable.CompareTo
        Return Me.CompareTo(DirectCast(obj, Person))
    End Function
 
    Public Function CompareTo(ByVal other As Person) As Integer _
    Implements IComparable(Of Person).CompareTo
        'Compare by LastName by default.
        Dim result As Integer = Me.LastName.CompareTo(other.LastName)
 
        'If LastNames are the same...
        If result = 0 Then
            '...compare by FirstName.
            result = Me.FirstName.CompareTo(other.FirstName)
        End If
 
        Return result
    End Function
 
End Class

Note that the Person class implements both the standard and generic forms of the IComparable interface, which is a good convention to follow. The Person.CompareTo method makes use of the String.CompareTo method. Using the CompareTo methods of primitive types in your own CompareTo methods is also a good convention to follow, where it’s feasible.

As you can see, two Person objects will first be compared by their LastName properties and then, if those are equivalent, they will be compared by their FirstName properties. Note that I say “equivalent” rather than “equal”. We might reimplement our CompareTo method to ignore case, in which case strings that are not equal may still be equivalent.

We can now sort an array or collection of Person objects automatically like so:

C#

List<Person> people = new List<Person>();
 
people.Add(new Person("Mary", "Smith"));
people.Add(new Person("John", "Williams"));
people.Add(new Person("John", "Smith"));
people.Add(new Person("Andrew", "Baxter"));
 
Console.WriteLine("Before sorting:");
 
foreach (Person person in people)
{
    Console.WriteLine("{0}, {1}",
                      person.LastName,
                      person.FirstName);
}
 
people.Sort();
 
Console.WriteLine("After sorting:");
 
foreach (Person person in people)
{
    Console.WriteLine("{0}, {1}",
                      person.LastName,
                      person.FirstName);
}

VB

Dim people As New List(Of Person)
 
people.Add(New Person("Mary", "Smith"))
people.Add(New Person("John", "Williams"))
people.Add(New Person("John", "Smith"))
people.Add(New Person("Andrew", "Baxter"))
 
Console.WriteLine("Before sorting:")
 
For Each person As Person In people
    Console.WriteLine("{0}, {1}", _
                      person.LastName, _
                      person.FirstName)
Next
 
people.Sort()
 
Console.WriteLine("After sorting:")
 
For Each person As Person In people
    Console.WriteLine("{0}, {1}", _
                      person.LastName, _
                      person.FirstName)
Next

In the next instalment we’ll look at how to perform custom sorting.

Part 2 here
Part 3 here