SyntaxStudy
Sign Up
C# GroupBy and Aggregation
C# Beginner 1 min read

GroupBy and Aggregation

`GroupBy` partitions a sequence into groups based on a key selector. Each group is an `IGrouping` that carries the key and implements `IEnumerable`. This is the LINQ equivalent of SQL's GROUP BY clause and is useful for computing per-category statistics, building dictionaries, and restructuring data. Aggregate functions like `Sum`, `Average`, `Min`, `Max`, and `Count` are immediately-executing operators that collapse a sequence to a single value. `Aggregate` (the general fold) applies a function cumulatively over a sequence with an optional seed value. `ToDictionary` and `ToLookup` materialise a sequence into dictionary-like structures. `ToLookup` supports multiple values per key (like a `Dictionary>`), whereas `ToDictionary` requires unique keys.
Example
// GroupBy, aggregation, and ToLookup

record Sale(string Region, string Product, decimal Amount, DateTime Date);

var sales = new List<Sale>
{
    new("North", "Widget",  120m, new DateTime(2024, 1, 5)),
    new("South", "Gadget",  250m, new DateTime(2024, 1, 7)),
    new("North", "Gadget",  180m, new DateTime(2024, 2, 3)),
    new("East",  "Widget",   90m, new DateTime(2024, 2, 14)),
    new("South", "Widget",  310m, new DateTime(2024, 3, 1)),
    new("North", "Widget",  200m, new DateTime(2024, 3, 18)),
};

// GroupBy region with aggregation
var byRegion = sales
    .GroupBy(s => s.Region)
    .Select(g => new
    {
        Region = g.Key,
        Total  = g.Sum(s => s.Amount),
        Count  = g.Count(),
        Avg    = g.Average(s => s.Amount)
    })
    .OrderByDescending(r => r.Total);

foreach (var r in byRegion)
    Console.WriteLine($"{r.Region,-8} Total={r.Total:C}  Avg={r.Avg:C}  Count={r.Count}");

// Multi-level grouping — by region then product
var detailed = sales
    .GroupBy(s => s.Region)
    .ToDictionary(
        g => g.Key,
        g => g.GroupBy(s => s.Product)
              .ToDictionary(pg => pg.Key, pg => pg.Sum(s => s.Amount)));

foreach (var (region, products) in detailed)
    foreach (var (product, total) in products)
        Console.WriteLine($"  {region}/{product}: {total:C}");

// ToLookup — allows multiple values per key (immutable after creation)
var lookup = sales.ToLookup(s => s.Product);
foreach (Sale northSale in lookup["Widget"])
    Console.WriteLine($"Widget sale: {northSale.Region} {northSale.Amount:C}");