1. Introduction
Old-school LINQ methods are like cafeteria food: you pick "sum", "min", or "average" and move on. But sometimes you want something special. That's where Aggregate comes in—like a chef cooking up your custom recipe.
If you compare it to functional programming, Aggregate is Reduce (or fold): you walk through a collection and step by step fold it into a single value. How exactly? That's up to you. Full control, full creativity.
Problems that are super convenient to solve with Aggregate:
- Getting tricky sums: like multiplying all numbers, or summing only even/odd ones, or summing by some custom rule.
- Joining strings with custom logic (like using a different separator for even and odd indexes).
- Building string reports ("Markdown lists", HTML, any custom format).
- Building collections with mutable state (like creating a dictionary from a list of objects by a custom key rule).
- Any complex calculation that's not covered by the standard aggregate functions.
2. Signature and How Aggregate Works
Let's peek at the official Microsoft docs for Enumerable.Aggregate:
public static TAccumulate Aggregate<TSource, TAccumulate>(
this IEnumerable<TSource> source,
TAccumulate seed,
Func<TAccumulate, TSource, TAccumulate> func
)
Here's what's what:
- source — your original collection.
- seed — the "starting value" (think of it as the initial accumulator).
- func — a function that takes two arguments: the accumulated value (acc), the current collection element, and returns the new accumulated value.
There's also a simpler overload:
public static TSource Aggregate<TSource>(
this IEnumerable<TSource> source,
Func<TSource, TSource, TSource> func
)
In this version, the "starting" value is the first element of the collection, and then func is called from the second to the last.
3. Basic Examples of Using Aggregate
Let's start with a little teaser for students: did you know that Aggregate can replace Sum or Product?
int[] numbers = { 2, 3, 4 };
// Calculate the sum
int sum = numbers.Aggregate((acc, val) => acc + val); // acc = accumulator, val = next element
// Calculate the product
int product = numbers.Aggregate((acc, val) => acc * val);
Console.WriteLine(sum); // 9
Console.WriteLine(product); // 24
Looks like Sum and Multiply, but we're doing it ourselves! You could joke: with Sum you always get a sum, but with Aggregate you can get a sum, an "anti-sum", or even a "sum of square roots".
4. Using Aggregate to Join Strings
Let's glue all the strings into one, separated by commas (no extra comma at the end):
string[] words = { "C#", "LINQ", "rocks" };
string result = words.Aggregate((acc, word) => acc + ", " + word);
// result: "C#, LINQ, rocks"
If the collection might be empty, the seed value is super handy:
// Start with an empty string
string report = words.Aggregate(
"Technologies: ",
(acc, word) => acc + word + "; ",
acc => acc.TrimEnd(' ', ';') // remove the extra ";" at the end
);
Console.WriteLine(report); // "Technologies: C#; LINQ; rocks"
Notice the third argument—a result selector function, available in the overload with seed. It's like "dessert processing" for the final dish.
5. Aggregate in Your App
Let's remember our student app we've been messing with for a few days now. Suppose we have a Student class:
public class Student
{
public string Name { get; set; }
public int Grade { get; set; }
}
List of students:
var students = new List<Student>
{
new Student { Name = "Alice", Grade = 5 },
new Student { Name = "Bob", Grade = 4 },
new Student { Name = "Vasya", Grade = 3 },
new Student { Name = "Maria", Grade = 5 }
};
Task: get a string like
"Best: Alice, Maria"
—that is, everyone with Grade == 5.
How beginners usually do it:
var best = "";
foreach (var s in students)
{
if (s.Grade == 5)
best += s.Name + ", ";
}
best = best.TrimEnd(',', ' ');
Console.WriteLine("Best: " + best);
Now—LINQ style with Aggregate:
var bestStr = students
.Where(s => s.Grade == 5)
.Select(s => s.Name)
.Aggregate("Best: ", (acc, name) => acc + name + ", ")
.TrimEnd(',', ' ');
Console.WriteLine(bestStr);
The beauty: minimum code, maximum readability. Even if your boss doesn't know LINQ, they'll get your idea (or at least appreciate the effort if not).
6. More Complex Scenarios
Actually, the accumulator in Aggregate can be anything—not just a number or string: a dictionary, your own class, or a struct.
For example: count the number of students for each grade from the student list:
var gradeCounts = students.Aggregate(
new Dictionary<int, int>(),
(dict, student) => {
if (dict.ContainsKey(student.Grade))
dict[student.Grade]++;
else
dict[student.Grade] = 1;
return dict;
}
);
// For output:
foreach (var pair in gradeCounts)
{
Console.WriteLine($"Grade {pair.Key}: {pair.Value} students");
}
This approach basically does GroupBy, but manually. Why not just use GroupBy? Sometimes you need special aggregation that's not in the standard LINQ methods, like summing only if the student isn't Vasya, or building a report for a report.
7. Visualization: How Aggregate Works (Flowchart)
Say we have an array { 2, 4, 3 } and want to accumulate the sum:
acc: 2 (first element)
|
v
val: 4
acc = acc + val = 2 + 4 = 6
|
v
val: 3
acc = acc + val = 6 + 3 = 9
|
v
[All elements processed]
|
v
Result: 9
Same scheme for the overload with seed:
seed: 0
|
v
val: 2
acc = 0 + 2 = 2
|
v
val: 4
acc = 2 + 4 = 6
|
v
val: 3
acc = 6 + 3 = 9
|
v
Result: 9
Comparing Aggregate and Other Aggregate Methods
| Method | Standard Behavior | Flexibility | For Empty Collections | Example |
|---|---|---|---|---|
|
Sums numbers | Low | Returns 0 | |
|
Counts elements | Low | Returns 0 | |
|
Any calculation | High | Needs seed | |
|
Joins strings | Medium | For empty = "" | |
8. Practical Tips, Common Mistakes, and Gotchas
Because it's so flexible, Aggregate can lead to surprises if you use it wrong. The most common mistakes:
Sometimes devs forget about the seed (starting value) and don't realize that if the collection is empty, the overload without seed will throw an exception (InvalidOperationException). So for empty collections, use the overload with seed:
var sum = new int[0].Aggregate(0, (acc, n) => acc + n); // Works! Returns 0
If you're accumulating a string with Aggregate, it's easy to get an extra separator (like a "," at the end). It's better to remove it with .TrimEnd(',', ' ')—or just use string.Join if you're just joining strings.
A mutable accumulator (like List or Dictionary) is often used in Aggregate, but be careful: if you mutate it in-place, all references point to the same object at each step. This can cause weird effects in parallel operations or if you expect copying. So in pure functional style, it's better to return a new object at each step, not mutate the old one.
In code used by others, try not to overcomplicate Aggregate just to look "cool": for newbies it seems harder than simple foreach or standard aggregates. If the task is basic—use Sum, Count, Join. But if you need to "build text by a special template"—Aggregate is your best friend!
9. Real-World Use, Interviews, and Frameworks
In the industry, the Aggregate method pops up a lot where you need unusual calculations or data folding, like building complex reports, stats, graphs, calculating hashes, generating unique ids, or even building UI components from collections with custom logic.
At interviews, LINQ questions almost always come up like: "How do you get the sum of array elements?", "How do you turn a list of strings into one string?" or "How do you count unique elements?"—and they often expect LINQ solutions, including Aggregate. Sometimes the questions are creative: "Can you use LINQ to sum the squares of even numbers, then return a string describing the process?"
In lots of popular .NET libraries and frameworks, like Entity Framework, Dapper, RavenDB, the Aggregate method is rarely used directly on the DB side, but in code it's super useful for in-memory aggregation.
GO TO FULL VERSION