Syntax
class X : IComparable<X> { public int CompareTo(X other) { } }Examples
Implementing IComparable
Defining a class's single natural ordering.
public class Student : IComparable<Student>
{
public string Name;
public int Grade;
public Student(string name, int grade) { Name = name; Grade = grade; }
public int CompareTo(Student? other) => Grade.CompareTo(other?.Grade);
public override string ToString() => $"{Name}({Grade})";
}
List<Student> students = new List<Student>
{
new Student("Fola", 85),
new Student("Zain", 92),
new Student("Jamal", 78)
};
students.Sort(); // uses CompareTo() automatically
Console.WriteLine(string.Join(", ", students)); // Jamal(78), Fola(85), Zain(92)Using a Custom Comparer
An IComparer<T> lets you sort the same objects in a different way without changing the class.
public class ByNameComparer : IComparer<Student>
{
public int Compare(Student? x, Student? y) => string.Compare(x?.Name, y?.Name);
}
List<Student> students = new List<Student>
{
new Student("Zain", 92),
new Student("Fola", 85)
};
students.Sort(new ByNameComparer());
Console.WriteLine(string.Join(", ", students)); // Fola(85), Zain(92)Sorting with a Lambda via Comparison<T>
For quick one-off custom sorts, a lambda is often simpler than a full IComparer<T> class.
List<Student> students = new List<Student>
{
new Student("Zain", 92),
new Student("Fola", 85),
new Student("Jamal", 78)
};
students.Sort((a, b) => b.Grade.CompareTo(a.Grade)); // descending by grade
Console.WriteLine(string.Join(", ", students)); // Zain(92), Fola(85), Jamal(78)Best practices
- Implement IComparable<T> when a class has one obvious, natural ordering (like numbers by value, or dates chronologically)
- Write a separate IComparer<T> class (or use a lambda) when you need multiple different orderings, or cannot modify the class being sorted
- Use a lambda passed to Sort()/OrderBy() for simple, one-off custom orderings instead of writing a full IComparer<T> class
- Handle null carefully in CompareTo()/Compare() implementations - a naive call can throw a NullReferenceException on unexpected null input
At a glance
- Purpose
- Applications on the .NET platform
- File extension
- .cs
- Runs in
- .NET runtime
- Usually used with
- .NET SDK and libraries
Specifications & further reading
Related C# documentation
List<T>
List<T> is a resizable, generic collection from System.Collections.Generic, and the most commonly used collection type in C#. Unlike a plain array, a List<T> automatically grows as elements are added, and it provides a rich set of methods for adding, removing, searching, and sorting. The <T> means a List can be strongly typed to hold any specific type, like List<string> or List<int>.Dictionary<TKey, TValue>
Dictionary<TKey, TValue> stores data as key-value pairs, offering fast average-case lookup, insertion, and deletion by key, backed by a hash table. Keys must be unique - adding a value with an existing key throws an exception, while indexer assignment (dict[key] = value) overwrites it instead. Dictionary does not guarantee any particular iteration order.Queue<T> & Stack<T>
Queue<T> is a first-in-first-out (FIFO) collection - items are added with Enqueue() and removed with Dequeue(), just like a real-world line. Stack<T> is last-in-first-out (LIFO) - items are added with Push() and removed with Pop(), like a stack of plates. Both are useful for specific processing orders where a general-purpose List<T> would require extra bookkeeping.HashSet<T>
HashSet<T> is a collection that stores unique elements with no guaranteed ordering, backed by a hash table. Adding a duplicate element has no effect, since HashSet automatically enforces uniqueness. It provides very fast average-case performance for adding, removing, and checking membership, and offers built-in set operations like union, intersection, and difference.
List<T> is a resizable, generic collection from System.Collections.Generic, and the most commonly used collection type in C#. Unlike a plain array, a List<T> automatically grows as elements are added, and it provides a rich set of methods for adding, removing, searching, and sorting. The <T> means a List can be strongly typed to hold any specific type, like List<string> or List<int>.Dictionary<TKey, TValue>
Dictionary<TKey, TValue> stores data as key-value pairs, offering fast average-case lookup, insertion, and deletion by key, backed by a hash table. Keys must be unique - adding a value with an existing key throws an exception, while indexer assignment (dict[key] = value) overwrites it instead. Dictionary does not guarantee any particular iteration order.Queue<T> & Stack<T>
Queue<T> is a first-in-first-out (FIFO) collection - items are added with Enqueue() and removed with Dequeue(), just like a real-world line. Stack<T> is last-in-first-out (LIFO) - items are added with Push() and removed with Pop(), like a stack of plates. Both are useful for specific processing orders where a general-purpose List<T> would require extra bookkeeping.HashSet<T>
HashSet<T> is a collection that stores unique elements with no guaranteed ordering, backed by a hash table. Adding a duplicate element has no effect, since HashSet automatically enforces uniqueness. It provides very fast average-case performance for adding, removing, and checking membership, and offers built-in set operations like union, intersection, and difference.