Skip to main content
๐Ÿ“œ WAYPOINT LESSON

The Operators That Do Real Work

โญโญ intermediateโณ 17 min read๐Ÿ“ Lesson 110 of 265

GroupBy, Join/GroupJoin, Aggregate, Zip/Chunk, and the set operations โ€” with the shapes they return.

GroupBy produces groups of pairs

GroupBy(keySel) yields IGrouping<TKey, TElement> โ€” a Key plus the members in encounter order. Counts, tops, and pivots all fall out:

var byCity = people.GroupBy(p => p.City);
foreach (var g in byCity)
    Console.WriteLine($"{g.Key}: {g.Count()}");

var topOfEach = people
    .GroupBy(p => p.City)
    .Select(g => g.OrderByDescending(p => p.Score).First());

ToLookup(keySel) is GroupBy's eager sibling: one pass, then O(1) indexing by key, and missing keys yield EMPTY groups instead of throwing.

Join and GroupJoin

Join pairs inner+outer on keys (flat rows); GroupJoin produces one outer item with a group of matches โ€” the shape of "customers with their orders, including zero-order customers":

var q = customers.GroupJoin(orders,
    c => c.Id, o => o.CustomerId,
    (c, cs) => (c.Name, Count: cs.Count()));

Left outer join = GroupJoin + SelectMany with an empty-group default.

Aggregate: fold a sequence into one value

int maxGap = diffs.Aggregate((acc, next) => Math.Max(acc, next));

Seed form Aggregate(seed, func, resultSelector) builds arbitrary results. Prefer a plain foreach when the fold needs explanation โ€” Aggregate is not a flex.

Windowing and sets

  • Zip(second) pairs element-wise (stops at shorter).
  • Chunk(size) splits into fixed-size batches (.NET 6+).
  • Distinct/Union/Intersect/Except compare by DEFAULT equality โ€” pass a comparer or project first for value semantics.

Check your understanding

  • GroupBy vs ToLookup โ€” which is lazy? (GroupBy; ToLookup runs immediately.)
  • Which join keeps customers with zero orders? (GroupJoin.)