The Aggregate method combines a sequence of elements into a single value. It does that by iterating over each one of them and calling a lambda function. This lambda function produces a result and carries it on to the next iteration.
The simplest form
The following example calculates the sum of some numbers:
public static void Main()
{
int[] numbers = [1, 2, 3, 4, 5];
int sum = numbers.Aggregate(
(currentTotal, currentElement)
=> currentTotal + currentElement);
Console.WriteLine($"The sum is: {sum}");
}- currentElement is the item of the array we are currently iterating over.
- currentTotal is the result of the previous iteration. On the first iteration, it will be the default value for that type, 0 for integers. This is also called the “accumulator” (more on that later)
In comparison, this is what it would look like without the use of Aggregate:
public static void Main()
{
int[] numbers = [1, 2, 3, 4, 5];
int sum = 0;
foreach (int number in numbers)
sum += number;
Console.WriteLine($"The sum is: {sum}");
}This is the crux of the Aggregate method. An operation which processes each item in the collection and passes its result to the next iteration. We just saw the simplest overload. Additionally, the .NET Class Library offers us two more interesting overloads, which we will explore next.
📦 The “accumulator”
The second overload allows us to specify a default value of the “accumulator”:
public static void Main()
{
int[] numbers = [1, 2, 3, 4, 5];
int product = numbers.Aggregate(1,
(currentTotal, currentElement)
=> currentTotal * currentElement);
Console.WriteLine($"The product is: {product}");
}The only difference with the previous example is that we now specify an integer as the first argument. This integer will be the value of the “accumulator” in the first iteration. This is critical in our example since we are calculating the product of numbers. The operation is a multiplication, and anything multiplied by 0 is always 0.
Without the Aggregate method, the previous example would look like this:
public static void Main()
{
int[] numbers = [1, 2, 3, 4, 5];
int product = 1;
foreach (int number in numbers)
product *= number;
Console.WriteLine($"The product is: {product}");
}Again, notice that we set the accumulator’s initial value to 1.
🏅 The “result selector”
Finally, we can take a look at the third overload:
public static void Main()
{
int[] numbers = [1, 2, 3, 4, 5];
string message = numbers.Aggregate(seed: 1,
func: (currentTotal, currentElement)
=> currentTotal * currentElement,
resultSelector: product => $"The final product is: {product}");
Console.WriteLine(message);
}Here we have a third argument, which is also a lambda function. This lambda is called “the result selector”; it will receive the final value of the accumulator,currentTotal and will perform an operation on it. In this example, it transforms it into the message “The final product is: 120”
Note: In this example we used named arguments for clarity.
📈 A diagram
Next, we provide another piece of code, followed by a diagram illustrating the steps of the execution (click to enlarge)
public static void Main()
{
int[] numbers = [7, 8, 9];
int product = numbers.Aggregate(seed: 1,
func: (currentTotal, currentElement)
=> currentTotal * currentElement);
Console.WriteLine($"The final product is: {product}");
}
💭 Terminology
Lastly, here is some terminology you are likely to encounter when people talk about the Aggregate method:
| Term | Variable name in our examples | Description |
|---|---|---|
| Input | numbers | The input. |
| Seed | seed | The initial value of the accumulator. |
| Accumulator | currentTotal | The accumulator. A value that gathers information on each iteration. |
| Operation | func | The operation to perform on each element of the input |
| Result selector | resultSelector | The operation to perform on the result value before returning. |
Happy coding!
Thanos
