Dataslope logoDataslope

Ordering and Grouping

OrderBy, ThenBy, GroupBy — the operators that bring shape to a flat sequence

So far our operators preserved the order of the input (Where, Select, SelectMany). This page is about operators that reorganize the sequence: sort it, or partition it into named groups.

Ordering: OrderBy, OrderByDescending, ThenBy

OrderBy produces a new sequence sorted by a key that you extract from each element with a selector lambda.

Code Block
C# 13

A few important details:

  • OrderBy returns IOrderedEnumerable<T> — a special kind of enumerable that remembers the sort key, so you can layer more sorting on top with ThenBy.
  • It's a stable sort: elements with equal keys keep their input order.
  • It allocates an internal buffer and sorts. It is not lazy in the same way Where is — it has to see all the elements before it can produce the first one in sorted order.

Multiple sort keys with ThenBy

When the primary key has ties, ThenBy decides them.

Code Block
C# 13

ThenBy is only available on IOrderedEnumerable<T> — that is, after an OrderBy. The compiler will stop you from writing xs.ThenBy(...) without an OrderBy first.

A common mistake: OrderBy followed by OrderBy

If you write xs.OrderBy(a).OrderBy(b), the second OrderBy discards the first one. The result is sorted by b, then by a only within tied b values — almost always the opposite of what you wanted. Use ThenBy instead.

Grouping: GroupBy

GroupBy partitions a sequence into groups by a key. Each group is itself an IEnumerable<T> (plus a Key property).

Code Block
C# 13

The result of GroupBy is an IEnumerable<IGrouping<TKey, TElement>>. IGrouping<TKey, TElement> is just an IEnumerable<T> that also has a Key property. That's it.

GroupBy with an aggregate

In practice, most of the time you don't iterate the groups — you project them into summaries.

Code Block
C# 13

This is one of the most common shapes you'll write in your career: group, aggregate, project, sort. Burn it into muscle memory.

Grouping by composite keys

The key can be anything — including a tuple or anonymous type.

Code Block
C# 13

Anonymous types implement structural equality, so two anonymous keys with the same field values are treated as equal — exactly what GroupBy needs.

GroupBy vs ToLookup

ToLookup is the immediate, eagerly-evaluated cousin of GroupBy. It returns a Lookup<TKey, TElement> you can index into.

Code Block
C# 13

When you need to index into groups (especially repeatedly), prefer ToLookup. When you're just iterating once, GroupBy is fine.

A multi-file challenge

Challenge
C# 13
Top region by sales

Implement SalesReport.TopRegion(IEnumerable<Sale> sales) which groups sales by Region, computes each region's total, and returns the name of the single region with the highest total.

Ties may be broken in any deterministic way (e.g. by region name).

Program.cs will print the returned region.

QuestionSelect one

What does xs.OrderBy(x => x.A).OrderBy(x => x.B) produce?

A sequence sorted by A, with ties in A broken by B.

A sequence sorted by B, with ties in B broken by A.

A compile error, because you can't apply OrderBy twice.

A sequence in undefined order.

On this page