Saturday, June 21, 2025

C# Simple Example of IComparer Part-1

IComparer exposes a method with signature: int Compare(object? x, object? y); that compares two externally supplied objects. The class that implements IComparer is generally called comparer. For example, the class that compares the names of employees of Employee class may be called NameComparer. Here, Employee and NameComparer are two separate classes. The two employee objects are supplied to NameComparer's Compare method.

The object that invokes Compare method does not belong to the types of objects which are being compared. The invoking object belongs to a separate type. In short, The comparer object is separate from the compared objects. 

Next, comes the question of two objects which are being compared. These two objects may or may not belong to same class. For example, we can compare names of Employee and Doctor classes. Here both Employee and Doctor are separate classes. Or, we can compare names of employees which belong to same Employee class.

Now, we consider about IComparable. It exposes a method with signature: int CompareTo(object? obj); Here, IComparable compares two objects of same class/type. The class that implements IComparable may be called Comparable class. So, Comparable class compares two objects of same class in the CompareTo method.

IComparable compares the current instance with another object of the same type and returns an integer that indicates whether the current instance precedes, follows, or occurs in the same position in the sort order as the other object.

Interface Who owns comparison logic? Compares
IComparable Inside the object this vs another object
IComparer Separate comparer class any two supplied objects

Both IComparer and IComparable are used in Array.Sort method to sort objects based on some property of the objects.

Example of same class objects comparison
The followiing example compares ages of two employees and array of employees can be used inside Array.Sort method to sort employees by age.
// Employee has Bonus
class Bonus
{
    public Bonus(int amount)
    {
        Amount = amount;
    }
    public int Amount { get; set; }
}
internal class Employee 
{
    private int _Age;
    // initialize data using CTOR
    public Employee(int id, string name, int age, int bonus)
    {
        Id = id;
        Name = name;
        Age = age;
        Bonus = new Bonus(bonus); // reference type
    }

    // to get data, we need properties
    public Bonus Bonus { get; set; }
    public int Id { get; } // read-only, Id cannot be updated
    public string? Name { get; set; }  // read-write, name can be updated
    public int Age // read-write, age can be updated
    {
        get { return _Age; }
        set
        {
            if (value < 18)
            {
                throw new ArgumentException("Age must be above 18");
            }
            else
            {
                _Age = value;
            }
        }
    }
}
The following NameComparer class implements IComparer non-generic interface. Note. Modern C# developers use IComparer<T> generic interface.
using System.Collections;

class NameComparer : IComparer
{
    public int Compare(object? x, object? y)
    {
        //step1, compare datatypes
        if (x is Employee e1 && y is Employee e2)
        {
            return string.Compare(e1.Name, e2.Name, StringComparison.OrdinalIgnoreCase);
        }
        else
        {
            throw new ArgumentException("Employees Data required.");
        }
    }
}
Inside Main method we use:
  • array of employees as 1st argument of Array.Sort static method and 
  • an instance of the class that implements IComparer as 2nd argument.
// array of employees
Employee[] employees = new Employee[5];
employees[0] = new Employee(1001,"Bhim",35,4000);
employees[1] = new Employee(1002,"Ajay",25,3500);
employees[2] = new Employee(1003,"Vijay",43,4200);
employees[3] = new Employee(1004,"Rakesh",27,3200);
employees[4] = new Employee(1005,"Mohan",22,3100);
Console.WriteLine("original array ==>");
foreach (var emp in employees)
{
    Console.WriteLine($"Id {emp.Id}, Name {emp.Name}, Age {emp.Age}");
}
Array.Sort(employees, new NameComparer());
Console.WriteLine("\nsorted by Age ==>");
foreach (var emp in employees)
{
    Console.WriteLine($"Age {emp.Age}, Id {emp.Id}, Name {emp.Name}");
}
OUTPUT
original array ==>
Id 1001, Name Bhim, Age 35
Id 1002, Name Ajay, Age 25
Id 1003, Name Vijay, Age 43
Id 1004, Name Rakesh, Age 27
Id 1005, Name Mohan, Age 22

sorted by Age ==>
Age 25, Id 1002, Name Ajay
Age 35, Id 1001, Name Bhim
Age 22, Id 1005, Name Mohan
Age 27, Id 1004, Name Rakesh
Age 43, Id 1003, Name Vijay

Generic version Example
The following code shows how NameComparer class implements IComparer<T> generic interface.
using System.Collections.Generic;

class NameComparer : IComparer<Employee>
{
    public int Compare(Employee? x, Employee? y)
    {
        if (x == null || y == null)
            throw new ArgumentException("Employees data required.");

        return string.Compare(
            x.Name,
            y.Name,
            StringComparison.OrdinalIgnoreCase);
    }
}
Generic version is type-safe.

No comments:

Post a Comment

Hot Topics