Back to Blog
C#

C# LINQ OrderBy: Syntax, Examples, and Sorting Behavior

c# linq orderby: Learn how to use C# LINQ OrderBy to sort collections, including OrderByDescending, ThenBy for multiple keys, custom comparers, and performance conside...

LINQSortingOrderByC# CollectionsIEnumerable
Illustration of C# LINQ OrderBy sorting a collection of objects into ascending order

When you need to sort a collection in C#, LINQ's OrderBy is the method most developers reach for. It orders elements by a key you select, and it works with any IEnumerable<T>. The basic syntax is straightforward, but the behavior around stability, deferred execution, and custom sorting is where many subtle issues appear. This article walks through the common patterns and the technical details that matter when you use c# linq orderby in real code.

Basic OrderBy Syntax

The simplest form of OrderBy takes a key selector function and returns an IOrderedEnumerable<T> that sorts the source sequence in ascending order. Consider a list of Product objects:

public class Product { public string Name { get; set; } public decimal Price { get; set; } } var products = new List<Product> { new Product { Name = "Laptop", Price = 1200m }, new Product { Name = "Mouse", Price = 25m }, new Product { Name = "Keyboard", Price = 80m } }; var sortedByPrice = products.OrderBy(p => p.Price);

The sortedByPrice sequence is lazily evaluated. It does not modify the original products list; it returns a new ordered sequence that enumerates the source in sorted order when you iterate over it. This deferred execution means the sort happens each time you enumerate, so if the source changes between enumerations, the result can change as well.

OrderByDescending for Descending Order

To sort in descending order, use OrderByDescending. The syntax mirrors OrderBy except the key selector is applied and the order is reversed:

var sortedByPriceDesc = products.OrderByDescending(p => p.Price);

Both methods return an IOrderedEnumerable<T>, which is important because that interface exposes the ThenBy and ThenByDescending methods used for secondary sorting.

Sorting by Multiple Keys with ThenBy

A single OrderBy call sorts by one key. When two items have the same key, their original relative order is preserved because LINQ's OrderBy is a stable sort. To add a secondary sort, chain ThenBy or ThenByDescending after the initial ordering. For example, sort products by category first, then by price within each category:

var sortedByCategoryThenPrice = products .OrderBy(p => p.Category) .ThenBy(p => p.Price);

The ThenBy method can only be called on an IOrderedEnumerable<T>, which is what OrderBy returns. If you try to call it on a plain IEnumerable<T>, the compiler will reject it. This design forces you to start with a primary sort and then add secondary keys in a predictable order.

Custom Sorting with IComparer<T>

Sometimes the default comparison for a type is not what you need. OrderBy accepts an optional IComparer<T> parameter that defines how keys are compared. This is useful for case-insensitive string sorting, custom business rules, or sorting by a property that does not implement IComparable.

var sortedByNameIgnoreCase = products.OrderBy(p => p.Name, StringComparer.OrdinalIgnoreCase);

For more complex logic, implement IComparer<T> yourself. Suppose you need to sort products by a custom priority value that is not directly on the object:

public class ProductPriorityComparer : IComparer<Product> { public int Compare(Product x, Product y) { // Custom logic: compare by a priority map var priorityMap = new Dictionary<string, int> { { "High", 1 }, { "Medium", 2 }, { "Low", 3 } }; int xPriority = priorityMap.TryGetValue(x.Priority, out int xVal) ? xVal : int.MaxValue; int yPriority = priorityMap.TryGetValue(y.Priority, out int yVal) ? yVal : int.MaxValue; return xPriority.CompareTo(yPriority); } } var sortedByPriority = products.OrderBy(p => p, new ProductPriorityComparer());

The comparer receives the entire object, so you can access any property or external data. This approach keeps the sorting logic encapsulated and testable.

Stability and Deferred Execution

OrderBy performs a stable sort, meaning that when two elements have equal keys, their original order in the source is preserved. This behavior is guaranteed by the LINQ to Objects implementation. It matters when you sort by multiple keys: the secondary sort only reorders elements that have identical primary keys.

Deferred execution also affects when the sort actually runs. The sort is not performed until you enumerate the result, such as with foreach, .ToList(), or .ToArray(). If the source collection is modified after the OrderBy call but before enumeration, the sort uses the modified data. This can lead to surprising results if you are not careful about the timing.

var query = products.OrderBy(p => p.Price); products.Add(new Product { Name = "Tablet", Price = 300m }); // When query is enumerated, the new product is included in the sort.

If you need a snapshot of the sorted data, materialize the query immediately with .ToList() or .ToArray().

Performance Considerations

Sorting is an O(n log n) operation, and OrderBy in LINQ to Objects uses a quicksort implementation. The key cost is the number of comparisons and the memory used for the sort. For large collections, the sorting algorithm is efficient, but there are a few practical concerns.

First, avoid sorting the same collection multiple times. If you need to sort by different keys in different parts of your code, consider sorting once and then using ThenBy to add secondary keys, rather than calling OrderBy repeatedly. Each OrderBy call creates a new sort operation.

Second, be mindful of the key selector's cost. If the key selector performs expensive computation, such as a database call or complex string manipulation, the cost is paid once per element during the sort. In such cases, projecting the key into a temporary anonymous type before sorting can reduce repeated work:

var sorted = products .Select(p => new { Product = p, SortKey = p.Category.ToLowerInvariant() }) .OrderBy(x => x.SortKey) .Select(x => x.Product);

This pattern is useful when the key is not a simple property access.

Third, remember that LINQ's OrderBy is not an in-place sort. It creates a new sequence and allocates memory for the sort. For very large collections, this can be a concern, but the memory usage is generally proportional to the number of elements and is acceptable in most applications.

Edge Cases: Null Values and Culture-Sensitive Sorting

When sorting by a key that can be null, the default comparer for reference types places null values first in ascending order. This behavior is consistent with the default Comparer<T>.Default, which treats null as less than any non-null value. If you need different handling, you can provide a custom comparer that explicitly controls the placement of null.

For strings, the default comparer uses the current culture, which can produce unexpected results in environments where the culture changes. For example, sorting product names with OrderBy(p => p.Name) may order strings differently on a German system versus a US system. To get a consistent, culture-invariant ordering, use StringComparer.Ordinal or StringComparer.OrdinalIgnoreCase:

var sortedByNameOrdinal = products.OrderBy(p => p.Name, StringComparer.Ordinal);

This ensures that the sort order is based on the Unicode code points, which is stable across cultures and operating systems. For most technical applications, ordinal comparison is the right choice unless you have a specific localization requirement.

Another edge case is sorting by a key that is a floating-point number with NaN. The default comparer for double and float treats NaN as less than all other values, which may not be what you expect. If you need to handle NaN specially, provide a custom comparer that defines the desired ordering.

Understanding these edge cases helps you avoid subtle bugs when the data does not conform to the default assumptions of the comparer. Always test your sorting logic with representative data, including null, empty strings, and special numeric values, to ensure the behavior matches your requirements.

c# linq orderby: Practical Usage and Code Examples | RYUSLOG DEV