Comparing Objects with IComparable and IComparer

There are two useful interfaces that are useful for comparing user-defined objects. These are the IComparable<T> and IComparer<T>interfaces. The IComparable interface is implemented in a class to allow comparison between it and another object. The IComparer<T>is implemented by a separate class which does the comparison of two objects. Note that older, non-generic versions are available to use but the generic versions are much easier and you won’t be needing to convert the objects to be compared.

The IComparable<T> Interface


Let’s first take a look at how we can use the IComparable interface. The following example shows a class which implements the IComparable<T> interface.

public class Person : IComparable<Person>
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public int Age { get; set; }
 
    public int CompareTo(Person other) 
    { 
     if (this.Age > other.Age) 
     return 1; 
     else if (this.Age < other.Age) 
     return -1; 
     else 
     return 0; 
    } 
}

Example 1

When a class uses the IComparable<T> interface, you need to create an implementation for its single method which is the CompareTo()method. The CompareTo() method returns an integer value. It also accepts one argument which is the object to be compared to the current object. Inside our CompareTo() method in   Example 1, we compared the age of the current person to the age of the other person passed to the method. By convention, if the current object is considered greater than the other object, then a value greater than 0 should be returned. We simply used 1 as the value but you can use any value greater than 0. If the current object is considered less than the other object, then a value less than 0 should be used as seen in line 12. If the two objects are considered equal, then the value 0 should be returned. The following code shows the Person object that implemented the IComparable<T> in action. The program will determine the youngest and the oldest among three Persons.

public class Program
{
    static void Main(string[] args)
    {
        Person person1 = new Person { FirstName = "John", LastName = "Smith", Age = 21 };
        Person person2 = new Person { FirstName = "Mark", LastName = "Logan", Age = 19 };
        Person person3 = new Person { FirstName = "Luke", LastName = "Adams", Age = 20 };
 
        Person youngest = GetYoungest(person1, person2, person3);
        Person oldest = GetOldest(person1, person2, person3);
 
        Console.WriteLine("The youngest person is {0} {1}.", 
            youngest.FirstName, youngest.LastName);
        Console.WriteLine("The oldest person is {0} {1}.", 
            oldest.FirstName, oldest.LastName);
        Console.ReadKey();
    }
 
    private static Person GetYoungest(Person person1, Person person2, Person person3)
    {
        Person youngest = person1; 
 
        if (person2.CompareTo(youngest) == -1) 
            youngest = person2;
 
        if (person3.CompareTo(youngest) == -1) 
            youngest = person3;
 
        return youngest;
    }
 
    private static Person GetOldest(Person person1, Person person2, Person person3)
    {
        Person oldest = person1; 
 
        if (person2.CompareTo(oldest) == 1) 
            oldest = person2;
 
        if (person3.CompareTo(oldest) == 1) 
            oldest = person3;
 
        return oldest;
    }
}

Example 2

The youngest person is Mark Logan.
The oldest person is John Smith.

Lines 5-7 creates three Person objects with sample values that we can use. Lines 9-10 creates variables that will hold references to the youngest and oldest Person. In line 9, we called the GetYoungest() method which is defined in lines 19-30. It accepts the three Persons that we will be comparing against each other. Line 21 assumes that the first person is the youngest. We test if person2 is younger than the current youngest person by using the implemented CompareTo() method of the IComparable<T> interface. Inside that method, the age of person2 is compared to the age of the current youngest person. If the person2‘s age is lower, then -1 should be returned and person2 will be considered as the new youngest person as seen in line 24. Line 26-27 uses the same technique to person3. After the comparisons, the youngest person is returned in line 29.

Line 10 calls the GetOldest() method which is defined in lines 32-43. The code is similar to the GetYoungest() method except that it compares if a person’s age is greater than the current oldest person. Therefore, we are expecting a return value of 1 instead of -1 when you call the CompareTo() method. Lines 12-15 prints the youngest and oldest person’s name.

The IComparer<T> Interface


The IComparer<T> is implemented by a seperate class. As the name of the interface suggests, implementing it makes a comparer class. For example, you can create multiple comparers for a Person class. You can make a comparer which compares the age, or a comparer which compares the FirstName or LastName of every person. The following examples uses the Person class created in   Example 1. We will be creating three comparer classes for the FirstNameLastName, and Age a person.

public class FirstNameComparer : IComparer<Person>
{
    public int Compare(Person person1, Person person2)
    {
        return person1.FirstName.CompareTo(person2.FirstName);
    }
}
 
public class LastNameComparer : IComparer<Person>
{
    public int Compare(Person person1, Person person2)
    {
        return person1.LastName.CompareTo(person2.LastName);
    }
}
 
public class AgeComparer : IComparer<Person>
{
    public int Compare(Person person1, Person person2)
    {
        return person1.CompareTo(person2);
    }
}

Example 3

Using the IComparer<T> class requires you to implement one method named Compare() which accepts two T objects and returns an intresult. Since we used IComparer<Person> interface, then the Compare() method will automatically have two Person parameters. The FirstNameComparer compares the first names of two persons being compared. Inside it’s Compare() method, we simply used the already defined CompareTo() method of the String class(since FirstName property is string) for simplicity and return the value it will return. We do the same for the LastNameComparer class but we compare the LastName of two persons instead. The Compare() method of the AgeComparer class uses the CompareTo() method of the actual Person class we defined in   Example 1 to save as from repeating the same code again. The Compare() method should return 0 if both parameters are considered equal, a value greater than 0 if the first parameter is greater than the second parameter, and a value less than 0 if the first parameter is less than the second parameter. The following program asks a user which property to use to sort a list of person.

public class Program
{
    static void Main(string[] args)
    {
        List<Person> persons = new List<Person> { 
                new Person { FirstName = "John", LastName = "Smith", Age = 21 },
                new Person { FirstName = "Mark", LastName = "Logan", Age = 19 },
                new Person { FirstName = "Luke", LastName = "Adams", Age = 20 }};
 
        Console.WriteLine("Original Order");
        foreach(Person p in persons)
            Console.WriteLine("{0} {1}, Age: {2}", p.FirstName, p.LastName, p.Age);
 
        Console.WriteLine("\nSort persons based on their:");
        Console.WriteLine("[1] FirstName\n[2] LastName\n[3]Age");
 
        Console.Write("Enter your choice: ");
        int choice = Int32.Parse(Console.ReadLine());
 
        ReorderPersons(choice, persons);
 
        Console.WriteLine("New Order");
        foreach (Person p in persons)
            Console.WriteLine("{0} {1}, Age: {2}", p.FirstName, p.LastName, p.Age);
    }
 
    private static void ReorderPersons(int choice, List<Person> persons)
    {
        IComparer<Person> comparer;
 
        if (choice == 1)
            comparer = new FirstNameComparer();
        else if (choice == 2)
            comparer = new LastNameComparer();
        else
            comparer = new AgeComparer();
 
        persons.Sort(comparer);
    }
}

Example 4

Original Order
John Smith, Age: 21
Mark Logan, Age: 19
Luke Adams, Age: 20

Sort persons based on their:
[1] FirstName
[2] LastName
[3]Age
Enter your choice: 1
New Order
John Smith, Age: 21
Luke Adams, Age: 20
Mark Logan, Age: 19
Original Order
John Smith, Age: 21
Mark Logan, Age: 19
Luke Adams, Age: 20

Sort persons based on their:
[1] FirstName
[2] LastName
[3]Age
Enter your choice: 2
New Order
Luke Adams, Age: 20
Mark Logan, Age: 19
John Smith, Age: 21
Original Order
John Smith, Age: 21
Mark Logan, Age: 19
Luke Adams, Age: 20

Sort persons based on their:
[1] FirstName
[2] LastName
[3]Age
Enter your choice: 3
New Order
Mark Logan, Age: 19
Luke Adams, Age: 20
John Smith, Age: 21

Lines 5-8 creates a List of Person objects containing three Person with predefined values for each property. Lines 11-12 shows the original order of the persons. Lines 14-15 shows the list of options that the user can choose as the basis of the sorting. Lines 17-18 asks the user of the his/her choice. Line 20 calls the ReorderPersons() method defined in lines 27-39. The method accepts the choice and the list of persons to sort. Inside the method, we defined a variable that will hold the type of comparer class to use. We used the IComparer<Person> as the type so it can hold any comparer class since they all implement this interface. We look at the different possible values of choice in lines 31-36 and assign the proper comparer based on the number value. In line 38, we used the Sort()method of the List<T> class. The Sort() method has an overloaded version which accepts an IComparer<T> object. We passed the created comparer class in line 29 containing whichever type of comparer it has based on the choice. The Sort() method will then sort the Person object using the comparer class that we provided.