1. Introduzione
I vecchi e buoni metodi LINQ — sono come il cibo in mensa: scegli "somma", "minimo" o "media" e vai avanti. Ma a volte vuoi qualcosa di speciale. Ed è qui che entra in gioco Aggregate — come uno chef che cucina secondo la tua ricetta.
Se lo confrontiamo con la programmazione funzionale, Aggregate è come Reduce (o fold): attraversi la collezione e la riduci passo dopo passo a un solo valore. Come farlo — lo decidi tu. Controllo totale, creatività totale.
Problemi che è comodo risolvere con Aggregate:
- Calcolare somme complesse: ad esempio, il prodotto di tutti i numeri, o la somma solo dei pari/dispari, o la somma secondo una regola particolare.
- Unire stringhe con logica personalizzata (ad esempio, con separatore diverso per indici pari e dispari).
- Costruire report testuali ("liste Markdown", HTML, qualsiasi formato custom).
- Costruire collezioni con stato modificabile (ad esempio, creare un dizionario da una lista di oggetti secondo una regola particolare per la chiave).
- Qualsiasi conteggio complesso che non rientra nelle funzioni di aggregazione standard.
2. Firma e principio di funzionamento del metodo Aggregate
Diamo un'occhiata alla documentazione ufficiale Microsoft su Enumerable.Aggregate:
public static TAccumulate Aggregate<TSource, TAccumulate>(
this IEnumerable<TSource> source,
TAccumulate seed,
Func<TAccumulate, TSource, TAccumulate> func
)
Ecco cosa significa tutto questo:
- source — la tua collezione di partenza.
- seed — "valore iniziale" (puoi vederlo come l'accumulatore di partenza).
- func — funzione che prende due argomenti: il valore accumulato (acc), l'elemento corrente della collezione e restituisce il nuovo valore accumulato.
C'è anche una versione più semplice:
public static TSource Aggregate<TSource>(
this IEnumerable<TSource> source,
Func<TSource, TSource, TSource> func
)
In questa variante il "valore iniziale" è il primo elemento della collezione, e poi func viene chiamata dal secondo all'ultimo.
3. Esempi base di utilizzo di Aggregate
Partiamo con una mini-intrigo per gli studenti: lo sapevate che Aggregate può sostituire Sum o Product?
int[] numbers = { 2, 3, 4 };
// Calcolare la somma
int sum = numbers.Aggregate((acc, val) => acc + val); // acc = accumulatore, val = elemento successivo
// Calcolare il prodotto
int product = numbers.Aggregate((acc, val) => acc * val);
Console.WriteLine(sum); // 9
Console.WriteLine(product); // 24
Sembra proprio Sum e Multiply, ma lo fai tu! Si può scherzare: con Sum puoi sempre calcolare la somma, ma con Aggregate — anche la "anti-somma" o la "somma delle radici quadrate".
4. Usare Aggregate per unire stringhe
Uniamo tutte le stringhe in una sola, separandole con una virgola (senza virgola in più alla fine):
string[] words = { "C#", "LINQ", "rocks" };
string result = words.Aggregate((acc, word) => acc + ", " + word);
// risultato: "C#, LINQ, rocks"
Se la collezione può essere vuota, il valore iniziale (seed) è molto comodo:
// Iniziamo con una stringa vuota
string report = words.Aggregate(
"Tecnologie: ",
(acc, word) => acc + word + "; ",
acc => acc.TrimEnd(' ', ';') // alla fine togliamo il ";" in più
);
Console.WriteLine(report); // "Tecnologie: C#; LINQ; rocks"
Nota il terzo argomento — la funzione di trasformazione del risultato (result selector), che c'è nella versione con seed. È come "il dessert" del piatto finale.
5. Aggregate in un'applicazione reale
Ricordiamoci della nostra app didattica, con cui giochiamo da giorni. Supponiamo di avere una classe Student:
public class Student
{
public string Name { get; set; }
public int Grade { get; set; }
}
Lista di studenti:
var students = new List<Student>
{
new Student { Name = "Alisa", Grade = 5 },
new Student { Name = "Bob", Grade = 4 },
new Student { Name = "Vasya", Grade = 3 },
new Student { Name = "Maria", Grade = 5 }
};
Obiettivo: ottenere una stringa tipo
"I migliori: Alisa, Maria"
— cioè tutti quelli con Grade == 5.
Come fanno di solito i principianti:
var best = "";
foreach (var s in students)
{
if (s.Grade == 5)
best += s.Name + ", ";
}
best = best.TrimEnd(',', ' ');
Console.WriteLine("I migliori: " + best);
E ora — stile LINQ con Aggregate:
var bestStr = students
.Where(s => s.Grade == 5)
.Select(s => s.Name)
.Aggregate("I migliori: ", (acc, name) => acc + name + ", ")
.TrimEnd(',', ' ');
Console.WriteLine(bestStr);
Bello: minimo codice, massima leggibilità. Anche se il tuo capo non conosce LINQ, capirà cosa vuoi fare (o almeno apprezzerà lo sforzo, se non capisce).
6. Scenari più complessi
In realtà, l'accumulatore in Aggregate può essere non solo un numero o una stringa, ma qualsiasi cosa: anche un dizionario, una tua classe o una struct.
Ad esempio: dalla lista di studenti, contare quanti studenti hanno ogni voto:
var gradeCounts = students.Aggregate(
new Dictionary<int, int>(),
(dict, student) => {
if (dict.ContainsKey(student.Grade))
dict[student.Grade]++;
else
dict[student.Grade] = 1;
return dict;
}
);
// Per stampare:
foreach (var pair in gradeCounts)
{
Console.WriteLine($"Voto {pair.Key}: {pair.Value} studenti");
}
Questo approccio in pratica realizza GroupBy, ma a mano. Perché non usare semplicemente GroupBy? A volte serve un'aggregazione particolare che non c'è nei metodi LINQ standard, tipo sommare solo se lo studente non è Vasya, o costruire un report per un report.
7. Visualizzazione: come funziona Aggregate (diagramma a blocchi)
Supponiamo di avere l'array { 2, 4, 3 } e vogliamo accumulare la somma:
acc: 2 (primo elemento)
|
v
val: 4
acc = acc + val = 2 + 4 = 6
|
v
val: 3
acc = acc + val = 6 + 3 = 9
|
v
[Tutti gli elementi sono stati processati]
|
v
Risultato: 9
Schema analogo per la versione con seed:
seed: 0
|
v
val: 2
acc = 0 + 2 = 2
|
v
val: 4
acc = 2 + 4 = 6
|
v
val: 3
acc = 6 + 3 = 9
|
v
Risultato: 9
Confronto tra Aggregate e altri metodi di aggregazione
| Metodo | Comportamento standard | Flessibilità | Per collezioni vuote | Esempio |
|---|---|---|---|---|
|
Somma i numeri | Bassa | Restituisce 0 | |
|
Conta gli elementi | Bassa | Restituisce 0 | |
|
Qualsiasi conteggio | Alta | Richiede seed | |
|
Unisce le stringhe | Media | Per vuote = "" | |
8. Consigli pratici, errori tipici e cose importanti
Per la sua flessibilità, Aggregate può portare a sorprese se usato male. Gli errori più comuni:
A volte i programmatori dimenticano il seed (valore iniziale) e non considerano che se la collezione è vuota, la versione senza seed lancia un'eccezione (InvalidOperationException). Quindi per collezioni vuote usa la versione con seed:
var sum = new int[0].Aggregate(0, (acc, n) => acc + n); // Funziona! Restituisce 0
Se accumuli una stringa con Aggregate, è facile avere un separatore in più (tipo "," alla fine). Meglio toglierlo con .TrimEnd(',', ' ') — oppure usa string.Join se devi solo unire stringhe.
Un accumulatore di tipo mutabile (tipo List o Dictionary) è spesso usato in Aggregate, ma attenzione: se lo modifichi in-place, a ogni passo tutti i riferimenti puntano allo stesso oggetto. Questo può dare effetti strani con operazioni parallele o se ti aspetti una copia. Quindi, in stile funzionale puro, meglio restituire un nuovo oggetto a ogni passo, non modificare quello vecchio.
Nel codice che usano altri, non complicare Aggregate solo per "fare il figo": ai principianti sembra più difficile dei semplici foreach o degli aggregati standard. Se il problema è banale — usa Sum, Count, Join. Ma se devi "scrivere testo secondo uno schema particolare" — Aggregate è il tuo migliore amico!
9. Collegamento con il lavoro reale, i colloqui e i framework
Nel mondo del lavoro il metodo Aggregate si trova spesso dove servono conteggi o riduzioni di dati non banali, ad esempio per creare report complessi, statistiche, grafi, calcolare hash, generare id unici o anche per costruire componenti UI da collezioni secondo logiche particolari.
Ai colloqui le domande su LINQ sono quasi sempre tipo: "Come calcolare la somma degli elementi di un array?", "Come trasformare una lista di stringhe in una sola stringa?" o "Come contare il numero di elementi unici?" — e spesso si aspettano soluzioni con LINQ e anche Aggregate. Le domande possono essere anche creative: "Sai con LINQ calcolare la somma dei quadrati dei numeri pari, e poi restituire una stringa che descrive il processo?"
In molte librerie e framework .NET popolari, come Entity Framework, Dapper, RavenDB, il metodo Aggregate raramente viene usato direttamente lato DB, ma a livello di codice è molto usato per aggregare in memoria.
GO TO FULL VERSION