Dataslope logoDataslope

Aggregations

Reducing a sequence to a single value — Count, Sum, Min, Max, Average, and the universal Aggregate

So far our operators have produced sequences. Aggregations are the operators that produce a single value. They're how you turn "a list of orders" into "the total revenue" or "the largest order".

This page covers the built-in shortcuts (Count, Sum, Min, Max, Average) and then the general-purpose tool that underlies all of them: Aggregate.

The friendly built-ins

OperatorWhat it returnsNotes
Count()Number of elementsWalks the sequence; for ICollection<T> it's O(1)
LongCount()Same, as longFor very large counts
Sum()Sum of elementsOverloads for numeric types and a selector
Min()Smallest elementThrows on an empty non-nullable sequence
Max()Largest elementSame caveat
Average()Arithmetic meanReturns double/decimal; throws on empty
MinBy(key)Element with smallest keyNew in .NET 6
MaxBy(key)Element with largest keySame
Code Block
C# 13

The lambda overloads (Sum(p => p.Price), Count(p => p.Stock > 0)) are the same as projecting first and then aggregating — just a shortcut.

Empty-sequence behavior

Aggregations on empty sequences are a famous source of bugs.

OperatorEmpty input
Count()0
Sum()0 (good!)
Min(), Max(), Average()Throws InvalidOperationException
MinBy(), MaxBy()Returns null
First()Throws
FirstOrDefault()Returns the default value

The pattern is: if the operator has a meaningful answer for "no elements" (count is 0, sum is 0), it returns that. If not, it throws — and you can usually pick a …OrDefault variant if you want a sentinel.

Code Block
C# 13

DefaultIfEmpty() yields one default element if the source is empty. It's a useful trick for "give me the min, or zero".

The universal aggregator: Aggregate

Aggregate (sometimes called fold or reduce elsewhere) is the general form. You give it:

  1. A seed — the starting accumulator value.
  2. An accumulator function(acc, element) => newAcc.
  3. Optionally, a result selector that turns the final accumulator into the answer.

Sum, Count, Min, and Max are all special cases.

Code Block
C# 13

Aggregate is the swiss-army knife. Any time you need to fold a sequence into a single value and there's no specialised operator already, reach for Aggregate.

Aggregating into a richer accumulator

The accumulator doesn't have to be a simple type. It can be a tuple, record, or even a collection — useful when you want to compute several things in one pass.

Code Block
C# 13

One pass through nums, five quantities computed. Compared with five separate Sum/Min/Max/Count/Sum calls (which would walk the source five times), this is potentially much more efficient — though for small in-memory collections the difference is negligible.

Aggregate is not always the best choice

Built-in operators are usually clearer and equally fast:

// Prefer this:
int sum = nums.Sum();

// Over this (works, but harder to read):
int sum = nums.Aggregate(0, (acc, n) => acc + n);

Reach for Aggregate when:

  • There is no built-in operator for what you want.
  • You need to compute several things in one pass.
  • The accumulator has a non-trivial type or update logic.

A walkthrough of one fold

Let's trace nums.Aggregate(0, (acc, n) => acc + n) for nums = [3, 1, 4]:

The accumulator threads through, one element at a time. The seed is the starting point; the final accumulator is the result.

A multi-file challenge

Challenge
C# 13
Compute a checksum

Implement Checksum.Compute(IEnumerable<int> xs) which returns a single int defined as:

Start at 0. For each element, multiply the accumulator by 31, then add the element. Return the accumulator.

That formula is a classic polynomial-rolling hash. Implement it with Aggregate — no foreach allowed.

QuestionSelect one

What does new int[0].Min() do?

Returns 0.

Returns int.MinValue.

Returns null.

Throws InvalidOperationException because the sequence is empty.

On this page