1. Introduzione
Il raggruppamento dei dati è il processo di dividere gli elementi di una collezione in "cestini" in base a una caratteristica comune. Immagina di ordinare le mele per colore: tutte le rosse in un cestino, le verdi in un altro, le gialle da un’altra parte. In programmazione questo si chiama raggruppamento.
Perché serve?
Nella vita reale spesso è utile raggruppare persone per città, prodotti per categoria, transazioni per data e così via. Questo permette di fare analisi utili: ad esempio, sapere quanti studenti ci sono in ogni classe, oppure quali città sono le più “studentesche”.
LINQ ti dà un metodo speciale per questo: GroupBy. C’è anche un operatore simile nella query syntax — group by. Vediamo tutto con esempi sulla nostra app didattica — un sistema per gestire studenti e classi.
2. GroupBy in method syntax
Firma base e risultato restituito
Il metodo GroupBy sembra un po’ spaventoso:
IEnumerable<IGrouping<TKey, TElement>> GroupBy<TElement, TKey>(
this IEnumerable<TElement> source,
Func<TElement, TKey> keySelector
)
Dove:
- source — la collezione di partenza (tipo una lista di studenti).
- keySelector — la funzione che decide su cosa raggruppare, tipo la proprietà ClassName o City.
Occhio!
Il risultato non è solo un array dei tuoi oggetti, ma una collezione di gruppi speciali (IGrouping<TKey, TElement>). Ogni gruppo contiene:
- La chiave del gruppo (Key — quello che ha unito gli elementi),
- La collezione degli elementi che sono finiti in quel gruppo.
Schema:
| Chiave (Key) | Elementi del gruppo (gruppo di oggetti) |
|---|---|
| "9A" | Ivan, Oleg, Maria |
| "10B" | Svetlana, Piotr |
| ... | ... |
Esempio 1: Raggruppiamo gli studenti per nome della classe
Supponiamo di avere una classe Student:
public class Student
{
public string Name { get; set; }
public string ClassName { get; set; }
public int Grade { get; set; }
}
E una collezione di studenti:
var students = new List<Student>
{
new Student { Name = "Ivan", ClassName = "9A", Grade = 5 },
new Student { Name = "Oleg", ClassName = "9A", Grade = 4 },
new Student { Name = "Maria", ClassName = "10B", Grade = 5 },
new Student { Name = "Svetlana", ClassName = "10B", Grade = 3 }
};
Raggruppiamo gli studenti per classe:
var studentsByClass = students.GroupBy(s => s.ClassName);
foreach (var group in studentsByClass)
{
Console.WriteLine($"Classe: {group.Key}"); // Chiave del gruppo
foreach (var student in group)
{
Console.WriteLine($" - {student.Name}, voto: {student.Grade}");
}
}
Cosa succede?
Il metodo GroupBy ci ha creato due gruppi: uno per "9A", l’altro per "10B". Scorrendo i gruppi, puoi stampare qualsiasi info aggregata su ogni “gruppetto”.
Visualizzazione: com’è la collezione dopo GroupBy
Pensala così:
students.GroupBy(s => s.ClassName)
├─ gruppo "9A" { Ivan, Oleg }
└─ gruppo "10B" { Maria, Svetlana }
Ogni “ramo” è una mini-lista di studenti uniti dalla stessa chiave.
Studenti per voto: raggruppamento per qualsiasi caratteristica
Raggruppiamo non per classe, ma per voto!
var studentsByGrade = students.GroupBy(s => s.Grade);
foreach (var group in studentsByGrade)
{
Console.WriteLine($"Voto: {group.Key}");
foreach (var student in group)
{
Console.WriteLine($" - {student.Name} dalla classe {student.ClassName}");
}
}
Output:
Voto: 5
- Ivan dalla classe 9A
- Maria dalla classe 10B
Voto: 4
- Oleg dalla classe 9A
Voto: 3
- Svetlana dalla classe 10B
3. Lavorare con i gruppi in LINQ: cosa puoi fare dopo GroupBy
Spesso dopo il raggruppamento non vuoi solo stampare i gruppi, ma ottenere qualche info aggregata — tipo contare quanti elementi ci sono in ogni gruppo, calcolare la media dei voti, raccogliere la lista dei nomi.
Esempio 2: Contare quanti studenti ci sono in ogni classe
var classCounts = students
.GroupBy(s => s.ClassName)
.Select(g => new { Class = g.Key, Count = g.Count() });
foreach (var cc in classCounts)
{
Console.WriteLine($"Nella classe {cc.Class} — {cc.Count} studenti");
}
Risultato:
Nella classe 9A — 2 studenti
Nella classe 10B — 2 studenti
- Prima abbiamo diviso gli studenti in gruppi.
- Poi per ogni gruppo abbiamo creato un oggetto anonimo con la chiave (nome della classe) e il conteggio (metodo Count()).
Esempio 3: Raccogliere la lista dei nomi degli studenti eccellenti per classe
var excellentByClass = students
.Where(s => s.Grade == 5)
.GroupBy(s => s.ClassName)
.Select(g => new
{
Class = g.Key,
ExcellentStudents = g.Select(s => s.Name).ToList()
});
foreach (var group in excellentByClass)
{
Console.WriteLine($"Nella classe {group.Class} eccellenti: {string.Join(", ", group.ExcellentStudents)}");
}
4. group by in query syntax
Se ti piace qualcosa che assomiglia a SQL, o vieni dal mondo dei database, LINQ supporta la sintassi delle query tipo group by ... into ....
Esempio base di raggruppamento
var studentsByClass =
from s in students
group s by s.ClassName into classGroup
select classGroup;
foreach (var group in studentsByClass)
{
Console.WriteLine($"Classe: {group.Key}");
foreach (var student in group)
{
Console.WriteLine($" - {student.Name}");
}
}
Prima “raggruppiamo” per una caratteristica, poi diamo un nome al gruppo (into classGroup) — e poi puoi usarlo come vuoi.
Proiezione con aggregati
Supponiamo di voler sapere le città e quanti studenti ci sono in ogni città:
var countsByCity =
from s in students
group s by s.ClassName into classGroup
select new { Class = classGroup.Key, Count = classGroup.Count() };
foreach (var item in countsByCity)
{
Console.WriteLine($"Nella classe {item.Class} — {item.Count} studenti");
}
— La sintassi è la stessa, ma dopo select puoi fare qualsiasi proiezione (aggregati, liste, medie ecc).
5. Scenari extra
Raggruppamento per più caratteristiche (chiave composta)
A volte serve raggruppare per una combinazione di proprietà. Tipo per classe e voto:
var byClassAndGrade = students
.GroupBy(s => new { s.ClassName, s.Grade });
foreach (var group in byClassAndGrade)
{
Console.WriteLine($"Classe: {group.Key.ClassName}, Voto: {group.Key.Grade}");
foreach (var student in group)
{
Console.WriteLine($" - {student.Name}");
}
}
La chiave del gruppo ora è un tipo anonimo (combinazione di più campi).
Come funzionano i gruppi nella pratica?
Un po’ di “cucina interna” di LINQ
- Quando fai GroupBy, LINQ costruisce internamente una specie di “tabella”: per ogni valore unico della chiave — un gruppo separato.
- I gruppi implementano l’interfaccia IGrouping<TKey, TElement>. Puoi accedere alla proprietà Key per ottenere il valore della caratteristica su cui hai raggruppato.
Lazy evaluation e performance
- I gruppi non vengono calcolati subito, ma solo quando inizi a scorrerli (foreach). Quindi, anche se hai collezioni enormi — non preoccuparti, i gruppi “vivono” solo quando li usi davvero.
- Se pensi di accedere più volte ai gruppi — puoi chiamare .ToList() o .ToArray() per “fissare” il risultato.
Applicazioni pratiche
Il raggruppamento ti permette di costruire report complessi, statistiche e analisi. Ecco qualche esempio reale dove può tornare utile:
- In un e-commerce: raggruppare gli ordini per utente, per mostrare la cronologia acquisti di ciascuno.
- Su una piattaforma educativa: raggruppare gli studenti per insegnante o per materia.
- In un’app HR: raggruppare i dipendenti per dipartimento e contare il personale.
- Al colloquio: potrebbero chiederti di scrivere un raggruppamento di dati reali con aggregazione dei valori.
LINQ ti permette di prototipare queste cose al volo, e saper leggere/scrivere raggruppamenti è un super plus per ogni dev .NET.
Schema visivo
Ecco un semplice diagramma a blocchi di come funziona GroupBy su una collezione:
[Collezione di studenti]
|
[GroupBy per ClassName]
|
[Gruppo "9A"] ---> [Ivan, Oleg]
[Gruppo "10B"] --> [Maria, Svetlana]
Ogni gruppo è una mini-collezione con la sua chiave.
6. Errori comuni e trappole
Alcuni studenti all’inizio si confondono per via dello specifico risultato di GroupBy:
- Dopo aver chiamato GroupBy non ottieni semplicemente “gruppi” tipo List<List<Student>>, ma una collezione di IGrouping<TKey, TElement>. Per lavorare con i gruppi usa la proprietà Key e itera sugli elementi del gruppo.
- Raggruppamento per chiave complessa (più campi): ricordati di usare un tipo anonimo o creare una classe separata se vuoi una chiave strutturata.
- Se ti serve solo sapere quanti elementi ci sono nei gruppi — non dimenticare di usare Select(g => g.Count()), altrimenti dovrai contare a mano gli elementi dentro ogni gruppo.
- A volte ci si può confondere con i cicli annidati: il primo ciclo sui gruppi, il secondo sugli elementi dentro il gruppo.
- Se sbagli la lambda o la chiave, ottieni gruppi diversi da quelli che ti aspettavi (o un unico grande gruppo se la chiave è uguale per tutti).
GO TO FULL VERSION