Question
Can you provide a short, descriptive, and comprehensive explanation of LINQ's Aggregate method in C#, including a small, clear example that shows how it works?
Short Answer
By the end of this page, you will understand how LINQ's Aggregate method combines all items in a sequence into one result. You will learn what an accumulator is, when to supply a seed value, how each overload behaves, and when clearer alternatives such as Sum or string.Join are preferable.
Concept
Aggregate is a LINQ method that reduces a sequence to one final value. It processes items one at a time and carries forward a running result.
For example, a list of numbers can become one total, and a list of words can become one sentence. The running result is called the accumulator.
int[] numbers = { 2, 3, 4 };
int product = numbers.Aggregate((currentTotal, nextNumber) =>
currentTotal * nextNumber);
Console.WriteLine(product); // 24
The lambda receives two values:
currentTotal: the result calculated so farnextNumber: the next item from the sequence
It must return the next value for currentTotal.
Aggregate is useful when no specialized LINQ method describes the operation. For standard tasks, prefer expressive methods such as Sum, Count, Min, Max, or string.Join. They make intent easier to recognize.
Mental Model
Imagine a conveyor belt and a small calculator.
- Items arrive on the belt one by one.
- The calculator stores a running answer.
- For every new item, it combines that item with the stored answer.
- When the belt is empty, the stored answer is the final result.
For multiplication of 2, 3, and 4, the calculator starts with 2, then computes 2 × 3, then computes 6 × 4. Its final stored answer is 24.
A seed is like setting the calculator's starting value before the belt begins. For addition, the natural seed is usually 0; for multiplication, it is usually 1.
Syntax and Examples
The most common Aggregate overloads are:
// Uses the first item as the initial accumulator.
TSource Aggregate<TSource>(Func<TSource, TSource, TSource> func)
// Starts with a supplied seed value.
TAccumulate Aggregate<TSource, TAccumulate>(
TAccumulate seed,
Func<TAccumulate, TSource, TAccumulate> func)
// Starts with a seed and converts the final accumulator to another result type.
TResult Aggregate<TSource, TAccumulate, TResult>(
TAccumulate seed,
Func<TAccumulate, TSource, TAccumulate> func,
Func<TAccumulate, TResult> resultSelector)
Example: calculate a total with a seed
int[] prices = { 12, 8, 5 };
int total = prices.Aggregate(
0,
(runningTotal, price) => runningTotal + price);
Console.WriteLine(total); // 25
0 is the seed. Each price is added to runningTotal.
Example: build a comma-separated string
string[] tags = { "csharp", "linq", "arrays" };
string text = tags.Aggregate(
"",
(result, tag) => result == "" ? tag : result + ", " + tag);
Console.WriteLine(text); // csharp, linq, arrays
This works, but string.Join(", ", tags) is usually simpler for joining strings.
Example: use a result selector
int[] scores = { 10, 20, 30 };
string report = scores.Aggregate(
0,
(total, score) => total + score,
total => $"Total score: {total}");
Console.WriteLine(report); // Total score: 60
The accumulator is an int, while the final result is a string.
Step by Step Execution
Consider this code:
int[] numbers = { 2, 3, 4 };
int product = numbers.Aggregate(
1,
(accumulator, number) => accumulator * number);
Execution trace:
| Step | accumulator before | number | Returned value |
|---|---|---|---|
| Start | 1 (the seed) | — | — |
| First item | 1 | 2 | 1 * 2 = 2 |
| Second item | 2 | 3 | 2 * 3 = 6 |
| Third item | 6 | 4 | 6 * 4 = 24 |
After the final item, Aggregate returns 24.
The accumulator function runs once per element when a seed is supplied. Without a seed, the first element becomes the initial accumulator, so the function runs once fewer than the number of elements.
Real World Use Cases
Aggregate can express custom reductions that do not have a dedicated LINQ method.
- Shopping cart calculation: accumulate a subtotal from line items.
- Permissions: combine permissions into a set of allowed actions.
- Data summaries: build totals, counts, and derived statistics in one custom result object.
- Request headers: combine header values into a dictionary when processing incoming data.
- Log or report generation: turn records into a formatted report when a specialized formatting API does not fit.
For example, build a frequency dictionary:
string[] words = { "cat", "dog", "cat" };
Dictionary<string, int> counts = words.Aggregate(
new Dictionary<string, int>(),
(result, word) =>
{
result[word] = result.GetValueOrDefault(word) + 1;
return result;
});
Console.WriteLine(counts["cat"]); // 2
In production code, be aware that this example mutates the dictionary accumulator. That is acceptable in many local LINQ pipelines, but should be documented and used deliberately.
Real Codebase Usage
In real C# projects, developers often choose the most specific operation first:
int total = prices.Sum(); // Better than Aggregate for a sum
int highest = scores.Max(); // Better than Aggregate for a maximum
string labels = string.Join(", ", tags); // Better than Aggregate for joining text
Use Aggregate when the result is genuinely custom. A common pattern is to seed it with a dedicated accumulator object:
public sealed class OrderSummary
{
public int ItemCount { get; set; }
public decimal Total { get; set; }
}
OrderSummary summary = items.Aggregate(
new OrderSummary(),
(result, item) =>
{
result.ItemCount++;
result.Total += item.Price * item.Quantity;
return result;
});
For input validation, developers commonly use All or Any instead of Aggregate because they communicate the intent clearly and can stop early:
bool allValid = users.All(user => user.Email.Contains('@'));
Aggregate always processes the sequence needed for its reduction; it is not usually the clearest tool for guard clauses or simple validation.
Common Mistakes
Omitting a seed for a possibly empty sequence
This overload throws InvalidOperationException when the source is empty:
int[] numbers = Array.Empty<int>();
int total = numbers.Aggregate((a, b) => a + b); // Throws
Provide an identity seed when an empty result has a sensible meaning:
int total = numbers.Aggregate(0, (a, b) => a + b); // 0
Choosing the wrong seed
A seed participates in the calculation, so it changes the answer.
int[] numbers = { 2, 3, 4 };
int product = numbers.Aggregate(0, (a, b) => a * b); // Always 0
Use 1 as the multiplication seed:
int product = numbers.Aggregate(1, (a, b) => a * b); // 24
Forgetting to return the accumulator
The accumulator function must return the next accumulator value.
// Does not compile: the lambda has no returned value.
// numbers.Aggregate(0, (total, number) => { total += number; });
Return the value:
int total = numbers.Aggregate(0, (total, number) =>
{
total += number;
return total;
});
Using Aggregate when a built-in method is clearer
int total = numbers.Aggregate(0, (a, b) => a + b);
This is valid, but the following communicates intent more directly:
int total = numbers.Sum();
Assuming the source collection changes
Aggregate returns a result. It does not replace or modify the source sequence itself. If the accumulator is a mutable object, that accumulator may be modified, but the source items are not automatically changed.
Comparisons
| Task | Preferred API | Why |
|---|---|---|
| Add numeric values | Sum | Direct and readable |
| Find a minimum or maximum | Min / Max | Expresses the operation clearly |
| Test whether all items match | All | Can stop as soon as an item fails |
| Test whether any item matches | Any | Can stop as soon as an item matches |
| Join strings | string.Join | Handles separators cleanly |
| Build a custom single result | Aggregate | Lets you control the accumulator logic |
Aggregate without a seed uses the first source item as its initial value. Aggregate with a seed is safer when the sequence may be empty and is required when the accumulator has a different type from the source items.
string[] words = { "a", "bb", "ccc" };
int totalLength = words.Aggregate(
0,
(total, word) => total + word.Length);
Here, source items are string, but the accumulator is an int, so a seed is necessary.
Cheat Sheet
using System.Linq;
// No seed: first element starts the accumulator.
var result = source.Aggregate((acc, item) => Combine(acc, item));
// Seed: use this for empty sequences or a different accumulator type.
var result = source.Aggregate(seed, (acc, item) => Combine(acc, item));
// Result selector: transform the final accumulator.
var result = source.Aggregate(
seed,
(acc, item) => Combine(acc, item),
acc => Transform(acc));
accis the running result.itemis the current source element.- The accumulator lambda must return the next
accvalue. - No-seed overload + empty source =
InvalidOperationException. - Seeded overload + empty source = returns the seed, or the result selector applied to the seed.
- Common identity seeds:
0for addition,1for multiplication,""for text. - Prefer
Sum,Min,Max,All,Any, andstring.Joinwhen they exactly express the task.
FAQ
What does LINQ Aggregate do in C#?
It reduces every item in a sequence into one final value by repeatedly applying an accumulator function.
What is the accumulator in Aggregate?
It is the running result. Each callback receives the current accumulator and the next source item, then returns the updated accumulator.
What is a seed value in LINQ Aggregate?
A seed is the initial accumulator value. It lets Aggregate handle empty sequences and lets the accumulator type differ from the item type.
Does Aggregate work with an empty list?
The overload without a seed throws on an empty sequence. A seeded overload returns the seed value, or applies its result selector to that seed.
Should I use Aggregate to add numbers?
Usually no. Prefer Sum() because it is shorter and communicates the intent more clearly.
What is the difference between Aggregate and Select?
Select transforms each item and returns a sequence of transformed items. Aggregate combines all items and returns one result.
Can Aggregate create an object or dictionary?
Yes. Seed it with the object or dictionary, update it for each item, and return it from the accumulator function.
Mini Project
Description
Create a small order-summary calculator. Given a collection of order lines, use Aggregate to calculate the number of units purchased and the total cost. This resembles the summary shown in a shopping cart or invoice.
Goal
Produce one OrderSummary object containing the total quantity and total price for all order lines.
Requirements
Use an OrderLine type with product name, unit price, and quantity.
Create at least three order lines.
Use Aggregate with an OrderSummary seed.
Add each line's quantity and line total to the summary.
Print the final unit count and total cost.
Keep learning
Related questions
AddTransient vs AddScoped vs AddSingleton in ASP.NET Core Dependency Injection
Learn the differences between AddTransient, AddScoped, and AddSingleton in ASP.NET Core DI with examples and practical usage.
Best Way to Repeat a Character in C#: Building Repeated Strings Efficiently
Learn the best way to repeat a character in C#, compare StringBuilder, string concatenation, and simpler built-in options.
C# Access Modifiers and static: public, private, protected, and Defaults
Learn how C# public, private, protected, and default access control visibility, and how static differs from instance members.