Lambda Expressions
IComparer
C# Programming
Software Development
Coding Practices

Using lambda expression in place of IComparer argument

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

In C#, methods like Array.Sort() and List.Sort() accept an IComparer<T> argument for custom sorting. Instead of creating a separate class that implements IComparer<T>, you can pass a lambda expression using Comparer<T>.Create(). This converts a two-parameter lambda into a valid IComparer<T> instance, making sort customization a one-liner instead of a multi-class affair.

The Traditional IComparer Approach

Without lambdas, custom sorting requires a dedicated class:

csharp
1class NameLengthComparer : IComparer<string>
2{
3    public int Compare(string x, string y)
4    {
5        return x.Length.CompareTo(y.Length);
6    }
7}
8
9// Usage
10string[] names = { "Charlie", "Alice", "Bob", "Diana" };
11Array.Sort(names, new NameLengthComparer());
12// Result: ["Bob", "Alice", "Diana", "Charlie"]

This works but creates boilerplate for every custom sort order.

Using Comparer<T>.Create with Lambda

Comparer<T>.Create() takes a Comparison<T> delegate (a lambda with two parameters) and wraps it in an IComparer<T>:

csharp
1string[] names = { "Charlie", "Alice", "Bob", "Diana" };
2
3// Sort by string length using a lambda
4Array.Sort(names, Comparer<string>.Create((x, y) => x.Length.CompareTo(y.Length)));
5// Result: ["Bob", "Alice", "Diana", "Charlie"]
6
7// Sort by last character
8Array.Sort(names, Comparer<string>.Create((x, y) => x[^1].CompareTo(y[^1])));

Direct Comparison<T> Delegate

Some overloads accept a Comparison<T> delegate directly, so you can skip Comparer<T>.Create:

csharp
1List<string> names = new() { "Charlie", "Alice", "Bob", "Diana" };
2
3// List.Sort accepts Comparison<T> directly
4names.Sort((x, y) => x.Length.CompareTo(y.Length));
5
6// Array.Sort also accepts Comparison<T>
7string[] arr = { "Charlie", "Alice", "Bob", "Diana" };
8Array.Sort(arr, (x, y) => x.Length.CompareTo(y.Length));

The difference: Sort(Comparison<T>) takes a delegate directly, while methods requiring IComparer<T> need Comparer<T>.Create().

Practical Examples

Sort Objects by Multiple Fields

csharp
1var people = new List<(string Name, int Age)>
2{
3    ("Alice", 30), ("Bob", 25), ("Charlie", 30), ("Diana", 25)
4};
5
6// Sort by age, then by name
7people.Sort((a, b) =>
8{
9    int ageCompare = a.Age.CompareTo(b.Age);
10    return ageCompare != 0 ? ageCompare : string.Compare(a.Name, b.Name);
11});
12// (Bob, 25), (Diana, 25), (Alice, 30), (Charlie, 30)

Descending Sort

csharp
1int[] numbers = { 3, 1, 4, 1, 5, 9 };
2
3// Ascending (default)
4Array.Sort(numbers, (x, y) => x.CompareTo(y));
5// 1, 1, 3, 4, 5, 9
6
7// Descending — swap x and y
8Array.Sort(numbers, (x, y) => y.CompareTo(x));
9// 9, 5, 4, 3, 1, 1

Case-Insensitive String Sort

csharp
1string[] words = { "banana", "Apple", "cherry", "Blueberry" };
2
3Array.Sort(words, (x, y) => string.Compare(x, y, StringComparison.OrdinalIgnoreCase));
4// Apple, banana, Blueberry, cherry

SortedSet and SortedDictionary

Collections that require IComparer<T> in their constructor need Comparer<T>.Create:

csharp
1// SortedSet with custom comparer
2var set = new SortedSet<string>(
3    Comparer<string>.Create((x, y) => x.Length.CompareTo(y.Length))
4);
5set.Add("Charlie");
6set.Add("Alice");
7set.Add("Bob");
8// Iteration order: Bob, Alice, Charlie (by length)
9
10// SortedDictionary with reverse key order
11var dict = new SortedDictionary<int, string>(
12    Comparer<int>.Create((x, y) => y.CompareTo(x))
13);
14dict[1] = "one";
15dict[3] = "three";
16dict[2] = "two";
17// Keys iterate: 3, 2, 1

LINQ OrderBy (No IComparer Needed)

LINQ's OrderBy and ThenBy use key selectors, which are even simpler than comparison lambdas:

csharp
1var sorted = names.OrderBy(n => n.Length).ThenBy(n => n);
2
3// But if you need a custom IComparer with LINQ:
4var sorted = names.OrderBy(n => n, Comparer<string>.Create(
5    (x, y) => x.Length.CompareTo(y.Length)
6));

When to Use Which

ScenarioApproach
List.Sort() or Array.Sort()Lambda directly: (x, y) => ...
Constructor requiring IComparer<T>Comparer<T>.Create((x, y) => ...)
Complex reusable comparison logicSeparate IComparer<T> class
LINQ sortingOrderBy(x => x.Property)

Common Pitfalls

  • Inconsistent comparison: A comparer must be consistent — if Compare(a, b) < 0, then Compare(b, a) > 0. An inconsistent lambda causes ArgumentException or unpredictable sort order.
  • Forgetting null handling: If the collection can contain nulls, the lambda must handle null arguments. x.CompareTo(y) throws NullReferenceException if x is null.
  • Using Comparer<T>.Create when Comparison<T> is accepted: List.Sort() and Array.Sort() accept Comparison<T> directly — no need for Comparer<T>.Create. Use Create only when the API specifically requires IComparer<T>.
  • Subtraction instead of CompareTo for integers: (x, y) => x - y works for small values but overflows for extreme int values (e.g., int.MinValue - 1). Always use x.CompareTo(y).
  • Mutating state in the comparer lambda: The comparison function should be pure — no side effects. Sorting algorithms call the comparer unpredictably many times, and side effects lead to bugs.

Summary

  • Use Comparer<T>.Create((x, y) => ...) to create an IComparer<T> from a lambda expression
  • List.Sort() and Array.Sort() accept Comparison<T> lambdas directly without Create
  • For SortedSet, SortedDictionary, and other constructors requiring IComparer<T>, use Comparer<T>.Create
  • Always use .CompareTo() instead of subtraction to avoid integer overflow
  • LINQ's OrderBy(x => x.Property) is simpler than comparison lambdas when you just need to sort by a key

Course illustration
Course illustration

All Rights Reserved.