How to Use Group By in C# LINQ
This article explains the group by operation in C#
Language Integrated Query (LINQ) and how to effectively process grouped
data. You will learn what group by does, understand the
underlying IGrouping<TKey, TElement> structure,
explore both query syntax and method syntax, and see how to iterate
over, project, aggregate, and group by multiple keys.
Understanding Group By in LINQ
The group by operator reorganizes collections into
sequences of buckets based on a specified key. Instead of returning a
flat list of items, LINQ grouping returns an
IEnumerable<IGrouping<TKey, TElement>>.
An IGrouping<TKey, TElement> is essentially a
collection with a Key property:
- Key: The shared value that defines the group.
- Elements: The items from the source collection that match the key.
Basic Grouping Example
Consider an Employee class:
public record Employee(string Name, string Department, decimal Salary);
var employees = new List<Employee>
{
new("Alice", "IT", 75000),
new("Bob", "HR", 50000),
new("Charlie", "IT", 80000),
new("Diana", "Finance", 90000),
new("Evan", "HR", 55000)
};Method Syntax
var groupedByDept = employees.GroupBy(e => e.Department);Query Syntax
var groupedByDept = from e in employees
group e by e.Department;How to Work with Grouping Results
Because each group contains both a key and an inner collection, you typically process the results using nested loops or projection.
Iterating Over Groups
To read grouped data, loop through each group to access the key, then loop through the group's elements:
foreach (var group in groupedByDept)
{
Console.WriteLine($"Department: {group.Key}");
foreach (var employee in group)
{
Console.WriteLine($" - {employee.Name} (${employee.Salary})");
}
}Projecting and Aggregating Results
Instead of working with raw IGrouping instances, you
often project the results into an anonymous type or a Data Transfer
Object (DTO) combined with aggregate functions like
Count(), Sum(), or Average().
Method Syntax
var departmentSummaries = employees
.GroupBy(e => e.Department)
.Select(g => new
{
Department = g.Key,
TotalEmployees = g.Count(),
TotalSalary = g.Sum(e => e.Salary),
AverageSalary = g.Average(e => e.Salary)
});
foreach (var dept in departmentSummaries)
{
Console.WriteLine($"{dept.Department}: Count = {dept.TotalEmployees}, Avg Salary = {dept.AverageSalary:C}");
}Query Syntax
In query syntax, use the into keyword to store grouping
results into a continuation variable before projecting:
var departmentSummaries = from e in employees
group e by e.Department into g
select new
{
Department = g.Key,
TotalEmployees = g.Count(),
TotalSalary = g.Sum(e => e.Salary)
};Grouping by Multiple Keys
To group by more than one property, group by an anonymous object. LINQ compares anonymous objects based on property values rather than reference equality.
var groupedByDeptAndSalaryTier = employees
.GroupBy(e => new
{
e.Department,
IsHighEarner = e.Salary >= 75000
})
.Select(g => new
{
g.Key.Department,
g.Key.IsHighEarner,
Employees = g.Select(e => e.Name).ToList()
});Selecting Custom Elements in Groups
If you only need specific properties within each group rather than
the entire source object, provide an element selector parameter to
GroupBy:
// Groups only employee names by department
var namesByDept = employees.GroupBy(
e => e.Department, // Key selector
e => e.Name // Element selector
);
foreach (var group in namesByDept)
{
Console.WriteLine($"{group.Key}: {string.Join(", ", group)}");
}