Sorting and comparers are used to sort, compare, and identify duplicate objects in collections such as List<T>, HashSet<T>, and Dictionary<TKey, TValue>.
C# provides three important interfaces:
-
IComparable<T>– Defines the default sorting order of an object. -
IComparer<T>– Defines custom sorting rules for objects. -
IEqualityComparer<T>– Defines custom equality rules for objects.
1. What is the difference between IComparable<T> and IComparer<T>?
What is IComparable<T>?
IComparable<T> is an interface used to define the default (natural) sorting order of objects.
It contains one method:
int CompareTo(T? other);
The class itself implements the comparison logic.
What is IComparer<T>?
IComparer<T> is an interface used to define custom sorting logic.
It contains one method:
int Compare(T? x, T? y);
The comparison logic is typically implemented in a separate class.
Comparison
|
Feature |
|
|
|---|---|---|
|
Purpose |
Default sorting |
Custom sorting |
|
Method |
|
|
|
Implemented in |
Object class |
Separate comparer class |
|
Compares |
Current object with another |
Two supplied objects |
|
Multiple sorting rules |
One default implementation |
Multiple comparers |
|
Used by |
|
|
Example Program
Suppose we have employees and want to sort them by ID or salary.
using System;
using System.Collections.Generic;
// Default sorting by ID
class Employee : IComparable<Employee>
{
public int Id { get; set; }
public string Name { get; set; } = "";
public int Salary { get; set; }
public int CompareTo(Employee? other)
{
if (other == null)
return 1;
return Id.CompareTo(other.Id);
}
}
// Custom sorting by Salary
class SalaryComparer : IComparer<Employee>
{
public int Compare(Employee? x, Employee? y)
{
if (ReferenceEquals(x, y)) return 0;
if (x == null) return -1;
if (y == null) return 1;
return x.Salary.CompareTo(y.Salary);
}
}
class Program
{
static void Main()
{
List<Employee> employees = new()
{
new Employee { Id = 3, Name = "John", Salary = 50000 },
new Employee { Id = 1, Name = "David", Salary = 70000 },
new Employee { Id = 2, Name = "Peter", Salary = 40000 }
};
// IComparable - Sort by ID
employees.Sort();
Console.WriteLine("Sort by ID:");
foreach (var emp in employees)
Console.WriteLine($"{emp.Id} - {emp.Name}");
// IComparer - Sort by Salary
employees.Sort(new SalaryComparer());
Console.WriteLine("Sort by Salary:");
foreach (var emp in employees)
Console.WriteLine($"{emp.Name} - {emp.Salary}");
}
}
Output:
Sort by ID:
1 - David
2 - Peter
3 - John
Sort by Salary:
Peter - 40000
John - 50000
David - 70000
Explanation
IComparable<T>:
-
EmployeeimplementsIComparable<Employee>. -
CompareTo()compares employee IDs. -
employees.Sort()automatically usesCompareTo(). -
Employees are sorted by ID.
IComparer<T>:
-
SalaryComparerimplementsIComparer<Employee>. -
Compare()compares salaries. -
employees.Sort(new SalaryComparer())uses custom sorting. -
Employees are sorted by salary.
How does comparison work?
Both methods follow the same comparison contract:
Compare(x, y)
|
v
Result < 0 --> x comes before y
Result = 0 --> x and y have equal sort order
Result > 0 --> x comes after y
Key Points
-
IComparable<T>defines the natural sorting order. -
IComparer<T>supports multiple custom sorting rules. -
CompareTo()compares the current object with another. -
Compare()compares two supplied objects. -
Both return negative, zero, or positive values.
-
Prefer
CompareTo()instead of subtracting numeric values because subtraction may overflow.
2. What is IEqualityComparer<T>, and when is it used?
What is it?
IEqualityComparer<T> is an interface used to determine whether two objects should be considered equal.
It is commonly used with:
-
HashSet<T> -
Dictionary<TKey, TValue> -
LINQ
Distinct() -
LINQ
GroupBy() -
LINQ
Except()
It contains two methods:
bool Equals(T? x, T? y);
int GetHashCode(T obj);
Why is it used?
Suppose two employee objects have the same employee ID but different names.
We want to treat them as duplicates based on the employee ID.
By default, a normal class uses reference equality unless equality behavior is overridden.
IEqualityComparer<T> allows us to define custom equality without modifying the employee class.
Example Program
using System;
using System.Collections.Generic;
using System.Linq;
class Employee
{
public int Id { get; set; }
public string Name { get; set; } = "";
}
class EmployeeComparer : IEqualityComparer<Employee>
{
public bool Equals(Employee? x, Employee? y)
{
if (ReferenceEquals(x, y)) return true;
if (x == null || y == null) return false;
return x.Id == y.Id;
}
public int GetHashCode(Employee obj)
{
return obj.Id.GetHashCode();
}
}
class Program
{
static void Main()
{
List<Employee> employees = new()
{
new Employee { Id = 1, Name = "John" },
new Employee { Id = 2, Name = "David" },
new Employee { Id = 1, Name = "Peter" }
};
var uniqueEmployees =
employees.Distinct(new EmployeeComparer());
foreach (var emp in uniqueEmployees)
{
Console.WriteLine($"{emp.Id} - {emp.Name}");
}
}
}
Output:
1 - John
2 - David
Explanation
-
Three employee objects are created.
-
John and Peter both have
Id = 1. -
EmployeeComparer.Equals()compares employee IDs. -
GetHashCode()calculates a hash code based on the ID. -
Distinct()treats John and Peter as duplicates. -
Only the first employee with ID 1 is returned.
How does it work?
Employee List
|
v
[1-John, 2-David, 1-Peter]
|
v
Distinct(EmployeeComparer)
|
v
GetHashCode()
|
v
Check candidate matches with Equals()
|
v
Remove duplicates
|
v
[1-John, 2-David]
Key Points
-
IEqualityComparer<T>defines custom equality. -
It implements
Equals()andGetHashCode(). -
Useful for duplicate detection and hash-based collections.
-
Two objects can be considered equal even when they are different instances.
-
IComparer<T>is for ordering;IEqualityComparer<T>is for equality. -
Hash codes should be based on the same properties used for equality.
3. What is the relationship between Equals() and GetHashCode()?
What is Equals()?
Equals() determines whether two objects are logically equal.
obj1.Equals(obj2);
Returns:
-
true– Objects are equal. -
false– Objects are not equal.
What is GetHashCode()?
GetHashCode() returns an integer hash code for an object.
Hash-based collections use hash codes to help locate stored elements efficiently.
Important Rule
If two objects are equal according to Equals(), they must return the same hash code.
Equals(x, y) == true
|
v
GetHashCode(x) == GetHashCode(y)
However, the reverse is not necessarily true.
Same Hash Code
|
v
Objects may be equal or unequal
|
v
Equals() determines actual equality
This is because different objects can have the same hash code, called a hash collision.
Example Program
using System;
class Employee
{
public int Id { get; set; }
public string Name { get; set; } = "";
public override bool Equals(object? obj)
{
if (obj is not Employee other)
return false;
return Id == other.Id;
}
public override int GetHashCode()
{
return Id.GetHashCode();
}
}
class Program
{
static void Main()
{
Employee emp1 = new()
{
Id = 1,
Name = "John"
};
Employee emp2 = new()
{
Id = 1,
Name = "David"
};
Console.WriteLine(emp1.Equals(emp2));
Console.WriteLine(emp1.GetHashCode());
Console.WriteLine(emp2.GetHashCode());
}
}
Output:
True
1
1
Explanation
-
emp1andemp2are different object instances. -
Both have the same employee ID.
-
Equals()compares their IDs and returnstrue. -
GetHashCode()uses the ID for hashing. -
Both objects return the same hash code.
What happens if GetHashCode() is not implemented correctly?
Suppose Equals() compares employee IDs, but GetHashCode() uses a different property such as Name.
Two employees with the same ID could then have different hash codes.
This breaks the equality contract and can cause incorrect behavior in HashSet<T> and Dictionary<TKey, TValue>.
Key Points
-
Equals()checks logical equality. -
GetHashCode()supports efficient hash-based lookups. -
Equal objects must have equal hash codes.
-
Equal hash codes do not guarantee equal objects.
-
Override
GetHashCode()whenever overriding value-basedEquals(). -
Avoid changing equality-related properties while an object is stored as a hash-based key.
4. What is the difference between Sort() and OrderBy()?
What is Sort()?
Sort() is a method available on List<T>.
It modifies the existing list by sorting its elements.
What is OrderBy()?
OrderBy() is a LINQ extension method.
It returns a new ordered sequence without modifying the original collection.
Comparison
|
Feature |
|
|
|---|---|---|
|
Namespace |
|
|
|
Modifies original list |
Yes |
No |
|
Return type |
|
|
|
Execution |
Immediate |
Deferred |
|
Multiple properties |
Custom comparer |
|
|
Descending order |
Custom comparer or |
|
|
Stable sorting |
Not guaranteed |
Yes |
|
Typical use |
Sort existing list |
LINQ queries |
Example Program
using System;
using System.Collections.Generic;
using System.Linq;
class Program
{
static void Main()
{
List<int> numbers = new()
{
40, 10, 30, 20
};
// Sort()
numbers.Sort();
Console.WriteLine("After Sort():");
Console.WriteLine(string.Join(", ", numbers));
List<int> values = new()
{
40, 10, 30, 20
};
// OrderBy()
var sortedValues = values.OrderBy(x => x);
Console.WriteLine("After OrderBy():");
Console.WriteLine(string.Join(", ", sortedValues));
Console.WriteLine("Original List:");
Console.WriteLine(string.Join(", ", values));
}
}
Output:
After Sort():
10, 20, 30, 40
After OrderBy():
10, 20, 30, 40
Original List:
40, 10, 30, 20
Explanation
Sort():
Original List
[40, 10, 30, 20]
|
v
Sort()
|
v
Same List Modified
[10, 20, 30, 40]
OrderBy():
Original List
[40, 10, 30, 20]
|
v
OrderBy(x => x)
|
v
New Ordered Sequence
[10, 20, 30, 40]
Original List Unchanged
[40, 10, 30, 20]
Important: Deferred Execution
OrderBy() does not perform sorting immediately when the query is created.
var result = numbers.OrderBy(x => x);
The sorting occurs when the sequence is enumerated:
foreach (var number in result)
{
Console.WriteLine(number);
}
Or when it is materialized:
List<int> sorted = numbers.OrderBy(x => x).ToList();
Real-Time Scenario
In an ASP.NET Core application:
var employees = employeeList
.OrderBy(e => e.Name)
.ToList();
This sorts an in-memory collection without modifying the original list.
With EF Core:
var employees = await dbContext.Employees
.OrderBy(e => e.Name)
.ToListAsync();
Here, EF Core normally translates OrderBy() into SQL ORDER BY, so the database performs the sorting.
Key Points
-
Sort()modifies the original list. -
OrderBy()returns an ordered sequence. -
Sort()executes immediately. -
LINQ
OrderBy()uses deferred execution for in-memory sequences. -
OrderBy()supportsThenBy()for multiple sorting properties. -
OrderBy()is stable, whileList<T>.Sort()is not guaranteed to be stable. -
Use database-side
OrderBy()for EF Core queries when appropriate.
5. How do you sort a list of custom objects by multiple properties?
What is it?
Multiple-property sorting means sorting objects using more than one field.
For example:
-
Sort employees by Department (ascending).
-
Within the same department, sort by Salary (descending).
-
If salaries are equal, sort by Name (ascending).
LINQ provides:
-
OrderBy()– Primary ascending sort. -
OrderByDescending()– Primary descending sort. -
ThenBy()– Secondary ascending sort. -
ThenByDescending()– Secondary descending sort.
Example Program
using System;
using System.Collections.Generic;
using System.Linq;
class Employee
{
public int Id { get; set; }
public string Name { get; set; } = "";
public string Department { get; set; } = "";
public int Salary { get; set; }
}
class Program
{
static void Main()
{
List<Employee> employees = new()
{
new Employee
{
Id = 1,
Name = "John",
Department = "IT",
Salary = 50000
},
new Employee
{
Id = 2,
Name = "David",
Department = "HR",
Salary = 40000
},
new Employee
{
Id = 3,
Name = "Peter",
Department = "IT",
Salary = 70000
},
new Employee
{
Id = 4,
Name = "Robert",
Department = "HR",
Salary = 60000
}
};
var sortedEmployees = employees
.OrderBy(e => e.Department)
.ThenByDescending(e => e.Salary)
.ThenBy(e => e.Name)
.ToList();
foreach (var emp in sortedEmployees)
{
Console.WriteLine(
$"{emp.Department} - {emp.Name} - {emp.Salary}");
}
}
}
Output:
HR - Robert - 60000
HR - David - 40000
IT - Peter - 70000
IT - John - 50000
Explanation
Step 1: Sort by Department
.OrderBy(e => e.Department)
Employees are grouped in ascending department order:
HR
HR
IT
IT
Step 2: Sort by Salary within each Department
.ThenByDescending(e => e.Salary)
HR
Robert - 60000
David - 40000
IT
Peter - 70000
John - 50000
Step 3: Sort by Name when Department and Salary are equal
.ThenBy(e => e.Name)
This acts as the third sorting condition.
Execution Flow
Employee List
|
v
OrderBy(Department)
|
v
HR Employees
IT Employees
|
v
ThenByDescending(Salary)
|
v
Highest Salary First
Within Each Department
|
v
ThenBy(Name)
|
v
Resolve Salary Ties by Name
|
v
Final Sorted List
Can we use Sort() for multiple properties?
Yes. We can use a custom comparer.
employees.Sort((x, y) =>
{
int result = string.Compare(
x.Department,
y.Department,
StringComparison.Ordinal);
if (result != 0)
return result;
result = y.Salary.CompareTo(x.Salary);
if (result != 0)
return result;
return string.Compare(
x.Name,
y.Name,
StringComparison.Ordinal);
});
Explanation:
-
Compare departments first.
-
If departments are equal, compare salaries in descending order.
-
If salaries are equal, compare names.
-
The original list is sorted in place.
Important Difference: OrderBy() vs ThenBy()
Consider:
employees
.OrderBy(e => e.Department)
.OrderBy(e => e.Salary);
The second OrderBy() starts a new primary ordering. It does not preserve Department as the primary sort key.
Correct approach:
employees
.OrderBy(e => e.Department)
.ThenBy(e => e.Salary);
Key Points
-
Use
OrderBy()for the primary sorting property. -
Use
ThenBy()for secondary sorting. -
Use
ThenByDescending()for descending secondary sorting. -
Multiple
ThenBy()calls are supported. -
Sort()with a custom comparer can also sort by multiple properties. -
Avoid using multiple
OrderBy()calls when you intend hierarchical sorting.
Important Interview Revision
|
Question |
Key Answer |
|---|---|
|
What is |
Defines default sorting using |
|
What is |
Defines custom sorting using |
|
What is |
Defines equality using |
|
|
Ordering vs equality comparison. |
|
Relationship between |
Equal objects must have equal hash codes. |
|
Does the same hash code mean equal objects? |
No. Hash collisions are possible. |
|
|
|
|
Does |
No, in-memory LINQ uses deferred execution. |
|
How to sort by multiple properties? |
|
|
What is stable sorting? |
Equal sort keys retain their original relative order. |
|
Does |
No. |
|
Does LINQ |
Yes, for LINQ to Objects. |
Most important interview concepts: IComparable<T> vs IComparer<T>, equality and hash-code contracts, Sort() vs OrderBy(), and multi-property sorting using ThenBy().