: C# 2008 Programmer

Implementing Comparison Using IComparer and IComparable

Implementing Comparison Using IComparer<T> and IComparable<T>

One of the tasks you often need to perform on a collection of objects is sorting. You need to know the order of the objects so that you can sort them accordingly. Objects that can be compared implement the IComparable interface (the generic equivalent of this interface is IComparable<T>). Consider the following example:

string[] Names = new string[] {
"John", "Howard", "Margaret", "Brian"
};
foreach (string n in Names)
Console.WriteLine(n);

Here, Names is a string array containing four strings. This code prints out the following:

John
Howard
Margaret
Brian

You can sort the Names array using the Sort() method from the abstract static class Array, like this:

Array.Sort(Names);
foreach (string n in Names)
Console.WriteLine(n);

Now the output is a sorted array of names:

Brian
Howard
John
Margaret

In this case, the reason the array of string can be sorted is because the String type itself implements the IComparable interface, so the Sort() method knows how to sort the array correctly. The same applies to other types such as int, single, float, and so on.

What if you have your own type and you want it to be sortable? Suppose that you have the Employee class defined as follows:

public class Employee {
public string FirstName { get; set; }
public string LastName { get; set; }
public int Salary { get; set; }
public override string ToString() {
return FirstName + ", " + LastName + " $" + Salary;
}
}

You can add several Employee objects to a List object, like this:

List<Employee> employees = new List<Employee>();
employees.Add(new Employee() {
FirstName = "John",
LastName = "Smith",
Salary = 4000
});
employees.Add(new Employee() {
FirstName = "Howard",
LastName = "Mark",
Salary = 1500
});
employees.Add(new Employee() {
FirstName = "Margaret",
LastName = "Anderson",
Salary = 3000
});
employees.Add(new Employee() {
FirstName = "Brian",
LastName = "Will",
Salary = 3000
});

To sort a List object containing your Employee objects, you can use the following:

employees.Sort();

However, this statement results in a runtime error (see Figure13-4) because the Sort() method does not know how Employee objects should be sorted.


Figure 13-4

To solve this problem, the Employee class needs to implement the IComparable<T> interface and then implement the CompareTo() method:

public class Employee : IComparable<Employee> {
public string FirstName { get; set; }
public string LastName { get; set; }
public int Salary { get; set; }
public override string ToString() {
return FirstName + ", " + LastName + " $" + Salary;
}
public int CompareTo(Employee emp) {
return this.FirstName.CompareTo(emp.FirstName);
}
}

The CompareTo() method takes an Employee parameter, and you compare the current instance (represented by this) of the Employee class's FirstName property to the parameter's FirstName property. Here, you use the CompareTo() method of the String class (FirstName is of String type) to perform the comparison.

The return value of the CompareTo(obj) method has the possible values as shown in the following table.

Value Meaning
Less than zero The current instance is less than obj.
Zero The current instance is equal to obj.
Greater than zero The current instance is greater than obj.

Now, when you sort the List object containing Employee objects, the Employee objects will be sorted by first name:

employees.Sort();
foreach (Employee emp in employees)
Console.WriteLine(emp.ToString());

These statements produce the following output:

Brian, Will $3000
Howard, Mark $1500
John, Smith $4000
Margaret, Anderson $3000

To sort the Employee objects using the LastName instead of FirstName, simply change the CompareTo() method as follows:

public int CompareTo(Employee emp) {
return this.LastName.CompareTo(emp.LastName);
}

The output becomes:

Margaret, Anderson $3000
Howard, Mark $1500
John, Smith $4000
Brian, Will $3000

Likewise, to sort by salary, you compare the Salary property:

public int CompareTo(Employee emp) {
return this.Salary.CompareTo(emp.Salary);
}

The output is now:

Howard, Mark $1500
Margaret, Anderson $3000
Brian, Will $3000
John, Smith $4000

Instead of using the CompareTo() method of the type you are comparing, you can manually perform the comparison, like this:

public int CompareTo(Employee emp) {
if (this.Salary < emp.Salary) return -1;
else if (this.Salary == emp.Salary) return 0;
else return 1;
}

How the Employee objects are sorted is fixed by the implementation of the CompareTo() method. If CompareTo() compares using the FirstName property, the sort is based on the FirstName property. To give users a choice of which field they want to use to sort the objects, you can use the IComparer<T> interface.

To do so, first declare a private class within the Employee class and call it SalaryComparer.

public class Employee : IComparable<Employee> {
private class SalaryComparer : IComparer<Employee> {
public int Compare(Employee e1, Employee e2) {
if (e1.Salary < e2.Salary) return -1;
else if (e1.Salary == e2.Salary) return 0;
else return 1;
}
}
public string FirstName { get; set; }
public string LastName { get; set; }
public int Salary { get; set; }
public override string ToString() {
return FirstName + ", " + LastName + " $" + Salary;
}
public int CompareTo(Employee emp) {
return this.FirstName.CompareTo(emp.FirstName);
}
}

The SalaryComparer class implements the IComparer<T> interface. IComparer<S> has one method Compare() that you need to implement. It compares the salary of two Employee objects.

To use the SalaryComparer class, declare the SalarySorter static property within the Employee class so that you can return an instance of the SalaryComparer class:

public class Employee : IComparable<Employee> {
private class SalaryComparer : IComparer<Employee> {
public int Compare(Employee e1, Employee e2) {
if (e1.Salary < e2.Salary) return -1;
else if (e1.Salary == e2.Salary) return 0;
else return 1;
}
}
public static IComparer<Employee> SalarySorter {
get { return new SalaryComparer(); }
}
public string FirstName { get; set; }
public string LastName { get; set; }
public int Salary { get; set; }
public override string ToString() {
return FirstName + ", " + LastName + " $" + Salary;
}
public int CompareTo(Employee emp) {
return this.FirstName.CompareTo(emp.FirstName);
}
}

You can now sort the Employee objects using the default, or specify the SalarySorter property:

employees.Sort(); //---sort using FirstName (default)---
employees.Sort(Employee.SalarySorter); //---sort using Salary---

To allow the Employee objects to be sorted using the LastName property, you could define another class (say LastNameComparer) that implements the IComparer<T> interface and then declare the SalarySorter static property, like this:

public class Employee : IComparable<Employee> {
private class SalaryComparer : IComparer<Employee> {
public int Compare(Employee e1, Employee e2) {
if (e1.Salary < e2.Salary) return -1;
else if (e1.Salary == e2.Salary) return 0;
else return 1;
}
}
private class LastNameComparer : IComparer<Employee> {
public int Compare(Employee e1, Employee e2) {
return e1.LastName.CompareTo(e2.LastName);
}
}
public static IComparer<Employee> SalarySorter {
get { return new SalaryComparer(); }
}
public static IComparer<Employee> LastNameSorter {
get { return new LastNameComparer(); }
}
public string FirstName { get; set; }
public string LastName { get; set; }
public int Salary { get; set; }
public override string ToString() {
return FirstName + ", " + LastName + " $" + Salary;
}
public int CompareTo(Employee emp) {
return this.FirstName.CompareTo(emp.FirstName);
}
}

You can now sort by LastName using the LastNameSorter property:

employees.Sort(Employee.LastNameSorter); //---sort using LastName---


: 1.381. /Cache: 3 / 1