Aggregation is a fairly common operation when it comes to data analysis, which is catered for pretty well in LINQ.

Given this type:

public record Spy(string Name, int Age, string Agency);

And this collection:

Spy[] spies =
[
  new Spy("James Bond (007)",50,"MI-6"),
  new Spy("Vesper Lynd",35,"MI-6"),
  new Spy("Q",30,"MI-6"),
  new Spy("Ethan Hunt",45,"IMF"),
  new Spy("Luther Stickell",48,"IMF"),
  new Spy("Benji Dunn",36,"IMF"),
  new Spy("Jason Bourne",55,"CIA"),
  new Spy("Harry Pearce",60,"MI-5"),
  new Spy("Adam Carter",40,"MI-5"),
  new Spy("Ros Myers",37,"MI-5")
];

How would you answer the question, “What is the total age of all spies per agency?”

Traditionally, you would use Group to group the collection by Agency and then aggregate by the sum of the age of each entry.

var analysis = spies.GroupBy(s => s.Agency)
	.Select(group => new { Agency = group.Key, TotalAge = group.Sum(spy => spy.Age) });

foreach (var entry in analysis)
{
  Console.WriteLine($"Total Age Of Agents In {entry.Agency} is {entry.TotalAge}");
}

This will print the following:

Total Age Of Agents In MI-6 is 115
Total Age Of Agents In IMF is 129
Total Age Of Agents In CIA is 55
Total Age Of Agents In MI-5 is 137

This has been improved in .NET 9, where a new method, AggregateBy has been introduced for similar scenarios.

The code looks like this:

var newAnalysis = spies.AggregateBy(x => x.Agency, 0,
	(total, spy) => total + spy.Age);

foreach (var entry in newAnalysis)
{
  Console.WriteLine($"Total Age Of Agents In {entry.Key} is {entry.Value}");
}

The magic is happening here:

spies.AggregateBy(x => x.Agency, 0,
	(total, spy) => total + spy.Age)

A couple of things are happening here:

  1. We are telling the compiler that we want to perform the aggregation per Agency
  2. We are initializing the total per aggregate to 0
  3. We are then adding to total the age of every Spy per Agency

The result is the same as before.

In the interest of clarity, I prefer to project the result into a new anonymous type with readable properties.

In this case, newAnalysis.Agency and newAnalysis.TotalAge is clearer than newAnalysis.Key and newAnalysis.Value.

So, a slight modification:

var newAnalysis = spies.AggregateBy(x => x.Agency, 0,
  (total, spy) => total + spy.Age)
  .Select(x => new { Agency = x.Key, TotalAge = x.Value });

foreach (var entry in newAnalysis)
{
  Console.WriteLine($"Total Age Of Agents In {entry.Agency} is {entry.TotalAge}");
}

Happy hacking!