C# Generics: Collection Interfaces
Pages: 1, 2, 3, 4, 5, 6, 7
Implementing IComparer
When you call Sort( ) on
the List, the default implementation of IComparer
is called, which uses QuickSort to call the
IComparable implementation of
CompareTo() on each element in the
List.
You are free to create your own implementation of
IComparer, which you might want to do if you need
control over how the sort ordering is defined. In the next example,
you will add a second field to Employee,
yearsOfSvc. You want to be able to sort the
Employee objects in the List on
either field, empID or
yearsOfSvc.
To accomplish this, create a custom implementation of
IComparer, which you pass to the
Sort() method of the List. This
IComparer class,
EmployeeComparer, knows about
Employee objects and knows how to sort them.
EmployeeComparer has the
WhichComparison property, of type
Employee. EmployeeComparer.ComparisonType:
public Employee.EmployeeComparer.ComparisonType
WhichComparison
{
get{return whichComparison;}
set{whichComparison = value;}
}
ComparisonType is an enumeration with two values,
empID or yearsOfSvc (indicating
that you want to sort by employee ID or years of service,
respectively):
public enum ComparisonType
{
EmpID,
YearsOfService
};
Before invoking Sort( ), create an instance of
EmployeeComparer and set its
ComparisionType property:
Employee.EmployeeComparer c = Employee.GetComparer();
c.WhichComparison=Employee.EmployeeComparer.ComparisonType.EmpID;
empArray.Sort(c);
When you invoke Sort( ), the
List calls the Compare method
on the EmployeeComparer, which in turn delegates
the comparison to the Employee.CompareTo() method,
passing in its WhichComparison
property:
public int Compare( Employee lhs, Employee rhs )
{
return lhs.CompareTo( rhs, WhichComparison );
}
The Employee object must implement a custom
version of
CompareTo( ), which takes the comparison and
compares the objects accordingly:
public int CompareTo(
Employee rhs,
Employee.EmployeeComparer.ComparisonType which)
{
switch (which)
{
case Employee.EmployeeComparer.ComparisonType.EmpID:
return this.empID.CompareTo(rhs.empID);
case Employee.EmployeeComparer.ComparisonType.Yrs:
return this.yearsOfSvc.CompareTo(rhs.yearsOfSvc);
}
return 0;
}
The complete source for this example is shown in Example 9-15. The integer array has been removed to
simplify the example, and the output of the
employee's ToString( ) method has
been enhanced to enable you to see the effects of the sort.
Example 9-15. Sorting an array by employees' IDs and years of service
#region Using directives
using System;
using System.Collections.Generic;
using System.Text;
#endregion
namespace IComparer
{
public class Employee : IComparable<Employee>
{
private int empID;
private int yearsOfSvc = 1;
public Employee( int empID )
{
this.empID = empID;
}
public Employee( int empID, int yearsOfSvc )
{
this.empID = empID;
this.yearsOfSvc = yearsOfSvc;
}
public override string ToString( )
{
return "ID: " + empID.ToString( ) +
". Years of Svc: " + yearsOfSvc.ToString( );
}
public bool Equals( Employee other )
{
if ( this.empID == other.empID )
{
return true;
}
else
{
return false;
}
}
// static method to get a Comparer object
public static EmployeeComparer GetComparer( )
{
return new Employee.EmployeeComparer( );
}
// Comparer delegates back to Employee
// Employee uses the integer's default
// CompareTo method
public int CompareTo( Employee rhs )
{
return this.empID.CompareTo( rhs.empID );
}
// Special implementation to be called by custom comparer
public int CompareTo(
Employee rhs,
Employee.EmployeeComparer.ComparisonType which )
{
switch ( which )
{
case Employee.EmployeeComparer.ComparisonType.EmpID:
return this.empID.CompareTo( rhs.empID );
case Employee.EmployeeComparer.ComparisonType.Yrs:
return this.yearsOfSvc.CompareTo( rhs.yearsOfSvc );
}
return 0;
}
// nested class which implements IComparer
public class EmployeeComparer : IComparer<Employee>
{
// private state variable
private Employee.EmployeeComparer.ComparisonType
whichComparison;
// enumeration of comparison types
public enum ComparisonType
{
EmpID,
Yrs
};
public bool Equals( Employee lhs, Employee rhs )
{
return this.Compare( lhs, rhs ) == 0;
}
public int GetHashCode(Employee e)
{
return e.GetHashCode( );
}
// Tell the Employee objects to compare themselves
public int Compare( Employee lhs, Employee rhs )
{
return lhs.CompareTo( rhs, WhichComparison );
}
public Employee.EmployeeComparer.ComparisonType
WhichComparison
{
get{return whichComparison;}
set{whichComparison = value;}
}
}
}
public class Tester
{
static void Main( )
{
List<Employee> empArray = new List<Employee>( );
// generate random numbers for
// both the integers and the
// employee id's
Random r = new Random( );
// populate the array
for ( int i = 0; i < 5; i++ )
{
// add a random employee id
empArray.Add(
new Employee(
r.Next( 10 ) + 100, r.Next( 20 )
)
);
}
// display all the contents of the Employee array
for ( int i = 0; i < empArray.Count; i++ )
{
Console.Write( "\n{0} ", empArray[i].ToString( ) );
}
Console.WriteLine( "\n" );
// sort and display the employee array
Employee.EmployeeComparer c = Employee.GetComparer( );
c.WhichComparison =
Employee.EmployeeComparer.ComparisonType.EmpID;
empArray.Sort( c );
// display all the contents of the Employee array
for ( int i = 0; i < empArray.Count; i++ )
{
Console.Write( "\n{0} ", empArray[i].ToString( ) );
}
Console.WriteLine( "\n" );
c.WhichComparison = Employee.EmployeeComparer.ComparisonType.Yrs;
empArray.Sort( c );
for ( int i = 0; i < empArray.Count; i++ )
{
Console.Write( "\n{0} ", empArray[i].ToString( ) );
}
Console.WriteLine( "\n" );
}
}
}
Output:
ID: 103. Years of Svc: 11
ID: 108. Years of Svc: 15
ID: 107. Years of Svc: 14
ID: 108. Years of Svc: 5
ID: 102. Years of Svc: 0
ID: 102. Years of Svc: 0
ID: 103. Years of Svc: 11
ID: 107. Years of Svc: 14
ID: 108. Years of Svc: 15
ID: 108. Years of Svc: 5
ID: 102. Years of Svc: 0
ID: 108. Years of Svc: 5
ID: 103. Years of Svc: 11
ID: 107. Years of Svc: 14
ID: 108. Years of Svc: 15
The first block of output shows the
Employee objects as they are added to the
List. The employee ID values and the years of
service are in random order. The second block shows the results of
sorting by the employee ID, and the third block shows the results of
sorting by years of service.
TIP: If you are creating your own collection, as in Example 9-11, and wish to implement
IComparer, you may need to ensure that all the types placed in the list implementIComparer(so that they may be sorted), by using constraints as described earlier.

