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.
A few important details:
OrderByreturnsIOrderedEnumerable<T>— a special kind of enumerable that remembers the sort key, so you can layer more sorting on top withThenBy.- 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
Whereis — 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.
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).
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.
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.
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.
When you need to index into groups (especially repeatedly), prefer
ToLookup. When you're just iterating once, GroupBy is fine.
A multi-file challenge
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.
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.