GroupBy în C# (.NET)

LINQ (Language Integrated Query) .NET 3.5+ (CountBy și AggregateBy în .NET 9+)

Grupează elementele unei secvențe după o cheie comună pentru a calcula statistici, sume și agregări pe categorii.

Sintaxă C#
var grupuri = colectie.GroupBy(x => x.Cheie); // fiecare grup g are g.Key și este IEnumerable<T>

Ce face GroupBy

Metoda GroupBy împarte o colecție în grupuri logice pe baza unei chei (o proprietate sau un tuplu de proprietăți), returnând o secvență de grupuri IEnumerable<IGrouping<TKey, TElement>>. Fiecare obiect g rezultat din grupare expune proprietatea g.Key (valoarea cheii după care s-a făcut gruparea) și implementează el însuși IEnumerable<TElement>, astfel încât poți aplica direct pe g metode de agregare precum g.Count(), g.Sum(...), g.Average(...) sau îl poți parcurge cu un foreach.

Atunci când vrei să transformi o colecție într-un dicționar în care cheia este unică (de exemplu ID-ul), folosești .ToDictionary(x => x.Id); când însă mai multe elemente împart aceeași cheie, folosești GroupBy (sau ToLookup(x => x.Categorie) pentru un dicționar imutabil unu-la-mulți).

Parametri, metode și opțiuni

Parametru / Metodă Descriere
source.GroupBy(x => x.Cheie) Grupează elementele după cheia returnată de expresia lambda; fiecare grup este un IGrouping<TKey, TSource>.
grup.Key Proprietatea care conține valoarea cheii comune pentru toate elementele din grupul curent.
source.GroupBy(cheie, (k, elem) => rez) Suprascriere care aplică direct o proiecție de agregare pe fiecare cheie k și elementele sale elem.
source.ToLookup(x => x.Cheie) Materializează imediat gruparea într-o structură indexată imutabilă ILookup<TKey, TElement>.
source.ToDictionary(x => x.Id, x => x) Convertește secvența într-un Dictionary<TKey, TValue> (necesită chei strict unice).

Exemple

Exemplul 1: Raport de vânzări pe categorii cu GroupBy, Count și Sum

Grupăm comenzile după județ, calculăm numărul de comenzi și valoarea totală pentru fiecare grup, apoi ordonăm grupurile descrescător după încasări.

var facturi = new[]
{
    new Factura("Cluj", 450m),
    new Factura("București", 1200m),
    new Factura("Cluj", 850m),
    new Factura("Timiș", 600m),
    new Factura("București", 950m),
    new Factura("Cluj", 300m)
};

var raportJudete = facturi
    .GroupBy(f => f.Judet)
    .Select(grup => new
    {
        Judet = grup.Key,
        NumarFacturi = grup.Count(),
        TotalIncasat = grup.Sum(f => f.Valoare),
        Medie = grup.Average(f => f.Valoare)
    })
    .OrderByDescending(r => r.TotalIncasat);

foreach (var r in raportJudete)
{
    Console.WriteLine($"{r.Judet,-10}: {r.NumarFacturi} facturi | Total: {r.TotalIncasat,7:F2} RON | Medie: {r.Medie:F2} RON");
}

record Factura(string Judet, decimal Valoare);

Ieșire în consolă

București : 2 facturi | Total: 2150.00 RON | Medie: 1075.00 RON
Cluj      : 3 facturi | Total: 1600.00 RON | Medie: 533.33 RON
Timiș     : 1 facturi | Total:  600.00 RON | Medie: 600.00 RON

Exemplul 2: Grupare după o cheie compusă (tuplu) și convertire în Dictionary

Putem grupa după mai multe proprietăți simultan folosind un tuplu (x.An, x.Trimestru).

var evenimente = new[]
{
    new { An = 2026, Trimestru = "Q1", Venit = 10000 },
    new { An = 2026, Trimestru = "Q1", Venit = 15000 },
    new { An = 2026, Trimestru = "Q2", Venit = 22000 }
};

var dictTrimestre = evenimente
    .GroupBy(e => (e.An, e.Trimestru))
    .ToDictionary(
        g => $"{g.Key.An}-{g.Key.Trimestru}",
        g => g.Sum(e => e.Venit)
    );

foreach (var (perioada, total) in dictTrimestre)
{
    Console.WriteLine($"{perioada}: {total} EUR");
}

Ieșire în consolă

2026-Q1: 25000 EUR
2026-Q2: 22000 EUR

Greșeli frecvente

  • Apelarea .ToDictionary(x => x.Categorie) pe o listă în care mai multe elemente au aceeași categorie — ToDictionary cere chei unice și aruncă ArgumentException: An item with the same key has already been added. Folosește GroupBy sau ToLookup când cheile se pot repeta.
  • Gruparea după un obiect mutabil sau o clasă care nu suprascrie Equals și GetHashCode — pentru chei compuse în GroupBy, folosește întotdeauna tupluri (x.A, x.B), tipuri anonime new { x.A, x.B } sau record, deoarece acestea implementează automat egalitatea structurală.

Funcții și concepte înrudite

  • Any, All, Count și Sum: Evaluează condiții booleene rapide (cu scurtcircuitare) și calculează agregări numerice (Count, Sum, Average, Min, Max) peste colecții.
  • Enumerable.Select: Proiectează (transformă) fiecare element al unei secvențe într-o formă nouă și aplatizează colecțiile imbricate cu SelectMany.
  • Dictionary<TKey, TValue>: Stochează perechi cheie-valoare într-o tabelă de dispersie (hash table) cu căutare, inserare și ștergere în timp constant O(1).
  • OrderBy / ThenBy: Sortează secvențele crescător sau descrescător pe baza unuia sau mai multor criterii și extrage pagini cu Skip și Take.