CodeGym /Corsi /C# SELF /Funzioni di aggregazione: S...

Funzioni di aggregazione: Sum, Count, Average, Max, Min

C# SELF
Livello 32 , Lezione 1
Disponibile

1. Introduzione

Quando lavori con le collezioni, spesso non ti serve solo scorrere ogni elemento e fare qualcosa, ma vuoi ottenere una specie di "riassunto" su tutto il set di dati. Per esempio:

  • Contare quanti studenti hanno preso il massimo dei voti.
  • Sapere quanti studenti ci sono in tutta la scuola.
  • Calcolare la somma totale dei punti presi da tutti gli studenti all'esame.
  • Trovare il voto massimo e minimo.
  • Calcolare la media della classe.

Certo, tutte queste cose si possono fare con un ciclo normale e una variabile contatore. Ma diciamocelo: chi ha voglia di scrivere venti righe di codice per una roba così semplice?

LINQ ti dà un set di funzioni di aggregazione — metodi già pronti che prendono una collezione, la scorrono tutta e ti danno la risposta: somma, media, massimo, minimo e a volte anche cose più furbe.

Ecco una mini "tabella degli aggregati":

Metodo Cosa fa Restituisce
Count()
Conta il numero di elementi
int
Sum()
Calcola la somma dei valori numerici Dipende dal tipo degli elementi (
int
,
double
, ...)
Average()
Calcola la media aritmetica
double
o altro tipo numerico
Max()
Cerca l'elemento massimo Elemento della collezione
Min()
Cerca l'elemento minimo Elemento della collezione

Tutti questi metodi sono extension methods per collezioni che implementano IEnumerable<T>. Per saperne di più — documentazione ufficiale LINQ metodi di aggregazione.

2. Prepariamo i dati per la pratica

Continuiamo a lavorare con una semplice app per gestire gli studenti. Ecco una classe base:


// Modello studente
public class Student
{
    public string Name { get; set; }
    public int Grade { get; set; } // Voto su scala da 1 a 5
    public string Email { get; set; }
}

E una lista:


// Lista base di studenti
List<Student> students = new List<Student>
{
    new Student { Name = "Ivan", Grade = 5, Email = "ivan@example.com" },
    new Student { Name = "Olga", Grade = 4, Email = "olga@example.com" },
    new Student { Name = "Artem", Grade = 3, Email = "artem@example.com" },
    new Student { Name = "Darya", Grade = 5, Email = "darya@example.com" },
    new Student { Name = "Petr", Grade = 2, Email = "petr@example.com" }
};

3. Conteggio degli elementi: Count()

Statistica base

Mettiamo che vuoi sapere quanti studenti hai in totale:


int totalStudents = students.Count(); // 5
Console.WriteLine($"Totale studenti: {totalStudents}");

LINQ-magia: Semplicissimo! Il metodo Count() restituisce il numero di elementi nella collezione.

Conteggio con condizione

Quanti sono gli studenti con il massimo dei voti?


int excellentStudents = students.Count(s => s.Grade == 5);
Console.WriteLine($"Studenti con 5: {excellentStudents}");

Qui passiamo a Count una lambda — restituirà solo quelli che rispettano la condizione (s.Grade == 5). Dentro LINQ è come fare Where(...).Count(), ma è più corto e va anche un po' meglio.

E se la collezione è vuota?

Se la collezione è vuota, Count restituisce tranquillamente 0 — nessun errore.

4. Sommare i valori: Sum()

Somma di tutti i voti

Mettiamo che vuoi sapere la somma totale dei voti presi dalla classe:


int sumOfGrades = students.Sum(s => s.Grade); // 5+4+3+5+2 = 19
Console.WriteLine($"Somma di tutti i voti: {sumOfGrades}");

Sum prende un selettore (lambda) che restituisce il valore per ogni elemento.

Somma su una collezione di numeri

Se hai solo una lista di numeri, il selettore non serve:


int[] numbers = { 1, 2, 3, 4, 5 };
int sum = numbers.Sum(); // 15

Errori comuni e particolarità

Se la collezione è vuota, Sum() per tipi numerici restituisce 0. Ma se la collezione è fatta di tipi nullable (int?, double?), Sum funziona comunque: ignora i valori null.

5. Media aritmetica: Average()

Calcoliamo la media della classe

Classico: quanto ha preso in media uno studente?


double averageGrade = students.Average(s => s.Grade); // (5+4+3+5+2)/5 = 3.8
Console.WriteLine($"Media voti: {averageGrade:F2}");

Average — super utile per le statistiche. Nota: il valore restituito è sempre double, anche se i valori di partenza erano int. Così non perdi la parte decimale.

Se non c'è nessun elemento

Se la collezione di partenza è vuota, chiamare Average() lancia un'eccezione InvalidOperationException. È una trappola classica: se non sei sicuro che la collezione abbia qualcosa, controlla prima!


if (students.Any())
    Console.WriteLine(students.Average(s => s.Grade));
else
    Console.WriteLine("Nessun dato per calcolare la media!");

Media su un array di numeri


double avg = numbers.Average();

6. Massimo e minimo: Max() e Min()

Chi è la star della classe e chi è in fondo

Vuoi sapere chi tira su la media della classe e chi... beh, fa preoccupare il prof? Facile:


int maxGrade = students.Max(s => s.Grade); // 5
int minGrade = students.Min(s => s.Grade); // 2

Console.WriteLine($"Voto massimo: {maxGrade}");
Console.WriteLine($"Voto minimo: {minGrade}");

Ma chi è questo eroe?

A volte non basta il numero, vuoi anche il nome di chi ha preso il voto più alto. Prendi l'oggetto studente col voto più alto:


// Prendi il primo con il voto più alto dopo aver ordinato in modo decrescente
var bestStudent = students.OrderByDescending(s => s.Grade).First();
Console.WriteLine($"Miglior studente: {bestStudent.Name} ({bestStudent.Grade})");

Nelle versioni più nuove di .NET c'è anche MaxBy, che lo fa ancora meglio. Da .NET 6 c'è già, e in .NET 9 hanno aggiunto altre comodità (vedi MaxBy su Microsoft Docs). Ma anche senza MaxBy puoi cavartela con l'ordinamento e .First().

Trappole e differenze

Se la collezione è vuota, chiamare Max() e Min() lancia anche qui InvalidOperationException. Quindi meglio essere pronti (soprattutto se non hai controllato la collezione prima):


if (students.Any())
    Console.WriteLine($"Voto massimo: {students.Max(s => s.Grade)}");
else
    Console.WriteLine("Nessuno studente per cercare il massimo.");

7. Esempi di "catene" di metodi di aggregazione

Spesso i metodi di aggregazione si usano insieme a filtri e proiezioni:


// Media tra chi ha preso 5
double avgExcellent = students
    .Where(s => s.Grade == 5)
    .Average(s => s.Grade); // sempre 5, ma l'esempio è chiaro

// Somma dei voti tra chi ha almeno 4
int sumGood = students
    .Where(s => s.Grade >= 4)
    .Sum(s => s.Grade);

// Numero di email uniche (per sicurezza)
int uniqueEmails = students
    .Select(s => s.Email)
    .Distinct()
    .Count();

Qui LINQ inizia a "giocare" davvero: combinare le operazioni ti permette di scrivere codice espressivo e leggibile, che capisce pure il tuo gatto (se il gatto è Junior C# Developer).

8. Confronto con il metodo "manuale": perché usare gli aggregati?

Per capire meglio, confrontiamo LINQ con i cicli normali per calcolare la media:

Metodo classico:


int sum = 0;
int count = 0;
foreach (var s in students)
{
    sum += s.Grade;
    count++;
}
double average = (count != 0) ? (double)sum / count : 0;

LINQ:


double average = students.Average(s => s.Grade);

Anche gestire la collezione vuota — è più facile!

9. Piccole particolarità utili

Due parole su performance e implementazione

Gli aggregati LINQ, a differenza della maggior parte delle operazioni LINQ, vengono eseguiti subito! Cioè, quando chiami Sum(), Count(), Average(), Max(), Min(), tutta la collezione viene già scorsa in quel momento. Questi metodi restituiscono un solo risultato finale, non una collezione.

È importante: se hai fatto qualcosa di pesante prima dell'aggregato, tipo un filtro o una trasformazione complicata — viene eseguito solo una volta, quando chiami l'aggregato.

Gli aggregati supportano la sintassi query?

Spesso chi inizia con LINQ chiede: "Ma posso scrivere tutto questo con la sintassi query?" Risposta breve: la sintassi query non ha parole chiave per gli aggregati, ma puoi sempre mischiarla con la method syntax:


var avg = (from s in students where s.Grade > 3 select s.Grade).Average();

Dentro le parentesi — normale query-syntax, poi chiami il metodo aggregato. Si fa più spesso di quanto pensi!

10. Errori tipici con LINQ

Errore n°1: provare a usare Average(), Max() o Min() su una collezione vuota.
Se la collezione è vuota, Sum() e Count() restituiscono tranquillamente 0, ma Average(), Max() e Min() lanciano un'eccezione. Prima di chiamare questi metodi, assicurati che la collezione abbia almeno un elemento.

Errore n°2: passare una lambda con la firma sbagliata.
Per esempio, se passi una stringa invece di un numero a una funzione di aggregazione (Sum, Max ecc.), ottieni un errore di compilazione. È facile sbagliare soprattutto con i metodi anonimi.

Errore n°3: controllo non ottimale del numero di elementi.
Il metodo Where(...).Count() prima crea una nuova collezione, poi conta gli elementi. Meglio usare Count(predicate) — conta subito quelli che vanno bene e va più veloce.

Errore n°4: ignorare le particolarità dei tipi nullable con l'aggregazione.
Se fai la somma su int?, Sum() ignora i valori null. È il comportamento giusto, ma a volte può darti risultati strani se non ci pensi prima.

Commenti
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION