codelessgenie blog

How to Sort a List in C# | List.Sort() Method Set -2

Sorting is a fundamental operation in programming, and in C#, the List<T> class provides a powerful Sort() method to sort elements in-place. This blog is the second part of our series on List.Sort(), focusing on advanced scenarios not covered in Set -1. We’ll explore custom comparers, sorting complex objects, handling null values, performance tradeoffs, and best practices to help you master sorting in C#.

2026-06

Table of Contents#

  1. Recap: List.Sort() Basics
  2. Deep Dive: IComparer Overload
  3. Custom Comparer Implementations
  4. Sorting Complex Objects with Multiple Criteria
  5. Handling Null Values in Sorting
  6. List.Sort() vs. LINQ OrderBy: When to Use Which?
  7. Common Pitfalls and How to Avoid Them
  8. Best Practices for List.Sort()
  9. Conclusion
  10. References

Recap: List.Sort() Basics#

Before diving into advanced topics, let’s recap the core List.Sort() overloads covered in Set -1:

OverloadDescription
Sort()Sorts the entire list using the default comparer (Comparer<T>.Default).
Sort(Comparison<T> comparison)Sorts using a custom Comparison<T> delegate (lambda/anonymous method).
Sort(int index, int count, IComparer<T> comparer)Sorts a sublist (from index with count elements) using a custom IComparer<T>.

In Set -2, we’ll focus on the IComparer<T> interface, custom sorting logic, and edge cases like nulls and complex objects.

Deep Dive: IComparer Overload#

The Sort(IComparer<T> comparer) overload lets you define custom sorting rules by implementing the IComparer<T> interface. This is ideal for reusable sorting logic or complex comparisons.

What is IComparer?#

IComparer<T> is a generic interface with a single method:

int Compare(T x, T y);  
  • Returns a negative number if x should come before y.
  • Returns zero if x and y are equal.
  • Returns a positive number if x should come after y.

Custom Comparer Implementations#

Let’s create practical examples of custom IComparer<T> implementations.

Example 1: Case-Insensitive String Comparer#

By default, string.Compare is case-sensitive. Let’s build a case-insensitive comparer:

using System;
using System.Collections.Generic;
 
public class CaseInsensitiveStringComparer : IComparer<string>
{
    public int Compare(string x, string y)
    {
        // Handle nulls: treat null as "less than" non-null
        if (x == null && y == null) return 0;
        if (x == null) return -1;
        if (y == null) return 1;
 
        // Compare strings case-insensitively
        return string.Compare(x, y, StringComparison.OrdinalIgnoreCase);
    }
}
 
// Usage:
var fruits = new List<string> { "apple", "Banana", "cherry", "Date" };
fruits.Sort(new CaseInsensitiveStringComparer());
// Result: ["apple", "Banana", "cherry", "Date"] (sorted A-Z, case-insensitive)

Example 2: Reverse Order Comparer#

Create a comparer to sort in reverse (descending) order:

public class ReverseComparer<T> : IComparer<T>
{
    private readonly IComparer<T> _baseComparer;
 
    public ReverseComparer(IComparer<T> baseComparer = null)
    {
        _baseComparer = baseComparer ?? Comparer<T>.Default;
    }
 
    public int Compare(T x, T y)
    {
        // Reverse the result of the base comparer
        return _baseComparer.Compare(y, x); 
    }
}
 
// Usage with integers:
var numbers = new List<int> { 3, 1, 4, 2 };
numbers.Sort(new ReverseComparer<int>()); 
// Result: [4, 3, 2, 1]

Sorting Complex Objects with Multiple Criteria#

For objects with multiple properties (e.g., Person, Product), you’ll often need to sort by multiple criteria (e.g., sort by Age, then by Name).

Example: Sorting a Person Class#

Define a Person class and sort by Age (ascending), then by Name (ascending):

public class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
 
    public override string ToString() => $"{Name} (Age: {Age})";
}
 
// Custom comparer for Person
public class PersonAgeThenNameComparer : IComparer<Person>
{
    public int Compare(Person x, Person y)
    {
        // Handle nulls (e.g., treat null as "less than" non-null)
        if (x == null && y == null) return 0;
        if (x == null) return -1;
        if (y == null) return 1;
 
        // First compare by Age
        int ageComparison = x.Age.CompareTo(y.Age);
        if (ageComparison != 0)
            return ageComparison;
 
        // If Ages are equal, compare by Name (case-insensitive)
        return string.Compare(x.Name, y.Name, StringComparison.OrdinalIgnoreCase);
    }
}
 
// Usage:
var people = new List<Person>
{
    new Person { Name = "Bob", Age = 30 },
    new Person { Name = "alice", Age = 25 },
    new Person { Name = "Charlie", Age = 25 },
    new Person { Name = "David", Age = 30 }
};
 
people.Sort(new PersonAgeThenNameComparer());
// Result: 
// alice (Age: 25), Charlie (Age: 25), Bob (Age: 30), David (Age: 30)

Handling Null Values in Sorting#

Lists may contain null elements. By default, Comparer<T>.Default throws a NullReferenceException when comparing null with non-null objects. To avoid this, explicitly handle nulls in your comparer.

Example: Sorting a List with Nulls#

Sort a list of strings, with nulls at the end:

public class NullsLastStringComparer : IComparer<string>
{
    public int Compare(string x, string y)
    {
        // Both null: equal
        if (x == null && y == null) return 0;
        // x is null: push to end (return 1, so x > y)
        if (x == null) return 1;
        // y is null: x comes first (return -1, so x < y)
        if (y == null) return -1;
        // Both non-null: use default string comparison
        return string.Compare(x, y);
    }
}
 
// Usage:
var words = new List<string> { "banana", null, "apple", null, "cherry" };
words.Sort(new NullsLastStringComparer());
// Result: ["apple", "banana", "cherry", null, null]

List.Sort() vs. LINQ OrderBy: When to Use Which?#

List.Sort() and LINQ’s OrderBy both sort data, but they behave differently:

FeatureList.Sort()LINQ OrderBy
Modifies OriginalYes (in-place sorting)No (returns a new IEnumerable<T>)
PerformanceFaster for large lists (no new list)Slower (allocates a new list)
FlexibilityLimited to List<T>Works with any IEnumerable<T>
SyntaxImperative (modifies state)Declarative (functional style)

When to Use List.Sort():#

  • You need to modify the original list (memory efficiency).
  • Working with large datasets (avoids allocating a new collection).

When to Use OrderBy:#

  • You need immutability (original list remains unchanged).
  • Chaining multiple sorting/filtering operations (e.g., OrderBy().ThenBy().Where()).
  • Sorting non-List<T> collections (e.g., Array, HashSet).

Example: OrderBy for Immutability

var numbers = new List<int> { 3, 1, 4, 2 };
var sortedNumbers = numbers.OrderBy(n => n).ToList(); 
// Original list remains [3, 1, 4, 2]; sortedNumbers is [1, 2, 3, 4]

Common Pitfalls and How to Avoid Them#

1. Not Handling Nulls#

Problem: Comparing null with non-null objects using Comparer<T>.Default throws NullReferenceException.
Fix: Explicitly handle nulls in custom comparers (as shown earlier).

2. Violating IComparer<T> Contract#

Problem: The Compare method must be transitive (if a < b and b < c, then a < c). Failing this leads to inconsistent sorting.
Fix: Test with edge cases (e.g., equal values, nulls) to ensure transitivity.

3. Modifying the List During Sorting#

Problem: If another thread modifies the list while Sort() is running, it may throw InvalidOperationException.
Fix: Lock the list during sorting or ensure thread safety.

4. Overusing OrderBy for Large Lists#

Problem: OrderBy creates a new list, which is inefficient for large datasets.
Fix: Use List.Sort() for in-place sorting when possible.

Best Practices for List.Sort()#

  1. Reuse Custom Comparers: Define IComparer<T> implementations as reusable classes (e.g., CaseInsensitiveStringComparer).
  2. Prefer Comparison<T> for Simple Logic: Use lambda expressions with Sort(Comparison<T>) for one-off sorting (e.g., list.Sort((x, y) => x.Age.CompareTo(y.Age))).
  3. Handle Nulls Explicitly: Always account for null elements in comparers to avoid exceptions.
  4. Test Edge Cases: Validate sorting with duplicates, nulls, and extreme values (e.g., int.MinValue).
  5. Choose Sort() for In-Place, OrderBy for Immutability: Match the method to your use case (memory vs. immutability).

Conclusion#

The List.Sort() method is a versatile tool for sorting in C#, with advanced capabilities through custom IComparer<T> implementations. By mastering comparers, handling nulls, and understanding performance tradeoffs, you can sort complex data efficiently and safely. Remember to choose between List.Sort() and LINQ OrderBy based on whether you need in-place modification or immutability.

References#