Using LINQ CountBy In .NET 9
[C#, .NET 9, LINQ]
Often in the course of your regular programming, you will need to do some basic analysis of data by aggregation, and the goto method here is typically LINQ and it’s GroupBy.
Take the following domain:
record Spy(string Name, string Service);
And the following dataset:
// Build a list of spies
Spy[] spies =
[
new Spy("James Bond (007)","MI-6"),
new Spy("Vesper Lynd","MI-6"),
new Spy("Q","MI-6"),
new Spy("Ethan Hunt","IMF"),
new Spy("Luther Stickell","IMF"),
new Spy("Benji Dunn","IMF"),
new Spy("Jason Bourne","CIA"),
new Spy("Harry Pearce","MI-5"),
new Spy("Adam Carter","MI-5"),
new Spy("Ros Myers","MI-5")
];
Suppose we need to know how many spies
does a Service
have?
A quick solution would be a simple GroupBy
as follows:
var results = spies.GroupBy(s => s.Service)
.Select(t => new { Service = t.Key, Count = t.Count() });
// Iterate through each result and print
foreach (var rawResult in results)
{
Console.WriteLine($"{rawResult.Service} : ({rawResult.Count})");
}
This groups the spies
by Service
and the creates an enumerable of a new type that has two properties - Service
which is the name of the service (gotten by the key) and Count
which is a computation of the number of spies
per service.
This will print the following:
MI-6 : (3)
IMF : (3)
CIA : (1)
MI-5 : (3)
This code can be simplified now with a new LINQ extension method - CountBy.
It will now look like this:
var results = spies.CountBy(s => s.Service);
foreach (var result in results)
{
Console.WriteLine($"{result.Key} : ({result.Value})");
}
Much terser, easier to read and easier to understand.
CountBy
cannot be levereaged by a property only - it can be something evaluated.
If we re-work our domain like this:
record Spy(string Name, int Age, string Service);
And our data like this:
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")
];
We can do this:
var results = spies.CountBy(s => s.Age >= 50);
foreach (var result in results)
{
Console.WriteLine($"{(result.Key ? "50 Or Older" : "Less Than 50")} : ({result.Value})");
}
Which prints this:
50 Or Older : (3)
Less Than 50 : (7)
You can also improve the clarity of your returned results by projecting them into anonymous types, because dealing with Key
and Value
can quickly get confusing.
var results = spies.CountBy(s => s.Age >= 50)
//Project the result into a new type
.Select(x => new { GreaterThanFifty = x.Key, Count = x.Value });
foreach (var result in results)
{
Console.WriteLine($"{(result.GreaterThanFifty ? "50 Or Older" : "Less Than 50")} : ({result.Count})");
}
Happy hacking!