LINQ in Practice · Lesson 17

Aggregate carries an explicit accumulator

Build a summary whose state and empty-input behavior are visible.

How it works

Aggregate folds a sequence into an accumulator. The seed is the initial state, and the update function combines that state with one source value at a time. Stats holds a sum and a count, so each iteration produces a new summary with one more value incorporated.

Starting from zero and zero, the sample processes two, three, and four. The final sum is nine and the count is three. Dividing those integers produces three for this data. That last calculation is deliberately outside the update function, making the difference between accumulated facts and a derived statistic visible.

The seed also defines empty-input behavior. An empty source returns the seed, which would leave Count equal to zero. Computing an average would then require a separate policy to avoid division by zero. Do not assume that supplying a seed automatically makes every later calculation valid for an empty collection.

Integer division discards a fractional remainder. If the required average can be fractional, convert to an appropriate numeric type before division and choose a display format. The accumulator's type is a design decision: a large dataset may require a wider integer or checked arithmetic even when each individual value is small.

Practice by adding minimum and maximum information to Stats. Decide how to represent those facts before the first value exists rather than inventing a misleading default minimum. Test an empty array, a single negative number, and a mixture of positive and negative numbers.

Finally, compare this implementation with calling Sum and Count separately. Aggregate can calculate several related facts in one traversal, but a more complicated accumulator is not automatically clearer or faster. Use it when shared traversal or an explicit state transition helps express the problem, and keep the update function free of unrelated side effects.

A working example

using System;
using System.Linq;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading;
using System.Threading.Tasks;
using System.Runtime.CompilerServices;

int[] values = { 2, 3, 4 };
var summary = values.Aggregate(new Stats(0, 0),
    (state, value) => new Stats(state.Sum + value, state.Count + 1));
Console.WriteLine($"{summary.Sum}:{summary.Count}:{summary.Sum / summary.Count}");
record Stats(int Sum, int Count);

Expected output

9:3:3
Try this
The seed describes the summary before any source element is processed.