CodeGym /Corsi /C# SELF /Nuovi metodi LINQ: CountBy...

Nuovi metodi LINQ: CountBy e AggregateBy

C# SELF
Livello 32 , Lezione 2
Disponibile

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)
"Neonville"
3 3
"Rosewater"
1 1
"Los Santos"
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à
GroupBy + Select + Count
students.CountBy(s => s.City)
Somma dei voti per città
GroupBy + Select + Sum
students.AggregateBy(..., 0, ...)
Voto massimo per città
GroupBy + Select + Max
students.AggregateBy(..., ...)
Frequenza dei caratteri in una stringa
GroupBy + Select + Count
word.CountBy(ch => ch)

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.

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