1. Introduzione
Se hai mai scritto una query LINQ "classica" con raggruppamento, tipo:
var cityCounts = students.GroupBy(s => s.City)
.Select(g => new { City = g.Key, Count = g.Count() });
di sicuro hai notato che per un semplice conteggio per gruppi il codice è un po' prolisso. Con l’uscita di .NET 9 il team Microsoft ha deciso di semplificarci la vita e ha aggiunto a LINQ due metodi super richiesti:
- CountBy — modo veloce per contare elementi per chiave.
- AggregateBy — aggregatore universale per chiave (non solo conta, ma fa anche somme, ecc).
Tra l’altro, questi metodi sono nati grazie a un sacco di richieste dalla community, e i loro analoghi esistono da tempo in molte librerie LINQ "avanzate", tipo MoreLINQ.
2. Metodo CountBy: conteggio conciso per gruppi
Descrizione
CountBy è letteralmente "raggruppa e poi conta subito". Il metodo restituisce una collezione di coppie "chiave-conteggio", che serve spessissimo quando analizzi dati: città top, numero di studenti per voto, frequenza di qualcosa.
Firma (semplificata):
IEnumerable<(TKey Key, int Count)> CountBy<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector)
Uso pratico
Esempio 1: Numero di studenti per città
Supponiamo di avere questa classe:
class Student
{
public string Name { get; set; }
public int Grade { get; set; }
public string City { get; set; }
}
E nella nostra app — una lista di studenti:
var students = new List<Student>
{
new Student { Name = "Ivan", Grade = 5, City = "Neonville" },
new Student { Name = "Anna", Grade = 4, City = "Los Santos" },
new Student { Name = "Egor", Grade = 3, City = "Neonville" },
new Student { Name = "Maria", Grade = 5, City = "Rosewater" },
new Student { Name = "Oleg", Grade = 4, City = "Neonville" }
};
In .NET 8 e versioni precedenti dovevi scrivere:
var group = students.GroupBy(s => s.City)
.Select(g => new { City = g.Key, Count = g.Count() });
foreach (var cityInfo in group)
{
Console.WriteLine($"{cityInfo.City}: {cityInfo.Count} studenti");
}
In .NET 9 ci pensa un solo metodo conciso:
var cityCounts = students.CountBy(s => s.City);
foreach (var (city, count) in cityCounts)
{
Console.WriteLine($"{city}: {count} studenti");
}
Sì, hai visto bene — niente GroupBy manuale, niente Count() calcolato a mano — tutto semplice e diretto!
Esempio 2: Conteggio per voti
Un classico per ogni preside — sapere quanti studenti eccellenti e quanti appena sufficienti ci sono nel gruppo:
var gradeCounts = students.CountBy(s => s.Grade);
foreach (var (grade, count) in gradeCounts)
{
Console.WriteLine($"Voto {grade}: {count} studenti");
}
Esempio 3: Frequenza dei caratteri in una stringa
CountBy non si usa solo con oggetti, ma anche con cose più semplici:
string word = "supercalifragilisticexpialidocious";
var charFrequencies = word.CountBy(ch => ch);
foreach (var (letter, count) in charFrequencies)
{
Console.WriteLine($"{letter} : {count}");
}
Come appare in memoria (illustrazione)
| Chiave | Count (prima di CountBy) | Count (con CountBy) |
|---|---|---|
|
3 | 3 |
|
1 | 1 |
|
1 | 1 |
Questo approccio rende il codice leggibile e riduce gli errori nei raggruppamenti.
Errori tipici e dettagli
Ricorda che CountBy restituisce una tupla (Key, Count), non un tipo anonimo. In certi casi sembra meno "parlante", ma è super trasparente. Inoltre non puoi specificare direttamente il tipo di numero per Count — restituisce sempre int.
3. Metodo AggregateBy: aggregati universali per gruppi
Descrizione
Se CountBy è solo conteggio per chiave, AggregateBy è il coltellino svizzero! Vuoi sommare le vendite per venditore, trovare il voto massimo per classe, o qualsiasi cosa che puoi esprimere con un accumulatore? AggregateBy fa tutto questo.
Firma (molto semplificata):
IEnumerable<(TKey Key, TAccumulate Result)> AggregateBy<TSource, TKey, TAccumulate>(
this IEnumerable<TSource> source,
Func<TSource, TKey> keySelector,
TAccumulate seed,
Func<TAccumulate, TSource, TAccumulate> func)
- keySelector — su cosa raggruppare.
- seed — valore iniziale per l’accumulazione.
- func — funzione che descrive come "accumulare" il risultato.
Uso pratico
Esempio 1: Somma dei voti per città
Invece di un LINQ mostruoso tipo:
var sums = students.GroupBy(s => s.City)
.Select(g => new { City = g.Key, Sum = g.Sum(s => s.Grade) });
Scrivi:
var sums = students.AggregateBy(
s => s.City, // raggruppiamo per città
0, // somma iniziale
(acc, s) => acc + s.Grade // aggiungiamo il voto
);
foreach (var (city, sum) in sums)
{
Console.WriteLine($"{city}: somma voti = {sum}");
}
Esempio 2: Voto massimo per città
Vuoi il valore massimo? Basta cambiare l’accumulatore:
var maxByCity = students.AggregateBy(
s => s.City,
int.MinValue, // valore iniziale — il minimo possibile
(max, s) => Math.Max(max, s.Grade)
);
foreach (var (city, maxGrade) in maxByCity)
{
Console.WriteLine($"{city}: voto massimo = {maxGrade}");
}
Esempio 3: Raccogliamo i nomi in una stringa
Aggiorniamo la nostra lista di studenti:
var namesByCity = students.AggregateBy(
s => s.City,
"", // stringa iniziale
(acc, s) => string.IsNullOrEmpty(acc) ? s.Name : acc + ", " + s.Name
);
foreach (var (city, names) in namesByCity)
{
Console.WriteLine($"{city}: {names}");
}
Illustrazione "dall’interno" (schema)
. {Student, Student, Student, ...}
|
| (raggruppiamo per city)
v
["Neonville"] ["Rosewater"] ["Los Santos"]
| | |
| (accumulazione) |
|___________________________|
|
v
{("Neonville", somma), ("Rosewater", somma), ...}
Confronto con i "vecchi" modi
Prima per tutte queste cose dovevi scrivere GroupBy + Select, dentro cui c’era il suo Sum, Aggregate o qualche altra roba. Ora AggregateBy nasconde questa "sofferenza" e rende il codice corto e chiaro.
Pratica: usiamolo in un’app
Il nostro progetto didattico — diario degli studenti. Aggiungiamo un report sulla media dei voti per città.
// Calcoliamo la media con AggregateBy — accumuliamo somma e conteggio, poi facciamo la media
var avgByCity = students.AggregateBy(
s => s.City,
(Sum: 0, Count: 0),
(acc, s) => (acc.Sum + s.Grade, acc.Count + 1)
).Select(x => (x.Key, Average: (double)x.Result.Sum / x.Result.Count));
foreach (var (city, avg) in avgByCity)
{
Console.WriteLine($"{city}: media voti = {avg:F2}");
}
Qui accumuliamo somma e conteggio in una tupla, poi dividiamo la somma per il conteggio per ottenere la media.
Dettagli e insidie
Se il tuo accumulatore è un tipo reference, occhio: dentro AggregateBy lo stesso riferimento viene usato per tutte le iterazioni del gruppo, quindi non mutare l’oggetto, altrimenti rischi di rovinare il risultato di tutto il gruppo.
E ancora: se vuoi non solo raggruppare ma anche trasformare i risultati — puoi usare subito .Select. Scegli sempre un seed (valore iniziale) semanticamente adatto, così eviti risultati strani (tipo non usare 0 per le stringhe).
4. Confronto tra vecchio e nuovo approccio
| Task | Vecchio LINQ | Nuovo LINQ .NET 9 |
|---|---|---|
| Numero di studenti per città | |
|
| Somma dei voti per città | |
|
| Voto massimo per città | |
|
| Frequenza dei caratteri in una stringa | |
|
Utilità pratica per lavoro e progetti reali
Metodi come CountBy e AggregateBy diventano indispensabili quando scrivi report statistici, prepari dati per visualizzazioni, costruisci tabelle pivot e altre cose tipiche. Tagliano il codice, lo rendono più leggibile e soprattutto più affidabile: meno "smanettamenti" manuali con raggruppamenti e aggregazioni, meno possibilità di sbagliare la logica.
A un colloquio per Junior/Middle, conoscere i nuovi metodi LINQ mostra che segui l’evoluzione della piattaforma, e in produzione — ti permette di non reinventare la ruota, ma usare costrutti eleganti e chiari, facili da leggere e mantenere.
GO TO FULL VERSION