Guide to LINQ ToLookup Collections in C
In C# LINQ, the ToLookup extension method creates an
ILookup<TKey, TElement> collection, which functions
as an immutable, one-to-many dictionary (often called a multimap). This
article provides a comprehensive overview of lookup collections,
explaining how ToLookup works, its core characteristics,
how it compares to Dictionary<TKey, TValue> and
GroupBy, and practical examples of how to use it in your
code.
What Is
ILookup<TKey, TElement>?
An ILookup<TKey, TElement> is a specialized
collection that maps a single key to an
IEnumerable<TElement> of values. Unlike standard
key-value pairs where each key must map to a single value, a lookup is
designed specifically to group multiple values under a single key.
When you call ToLookup on an
IEnumerable<T>, LINQ immediately iterates through the
source sequence, evaluates all elements, and organizes them into an
organized, indexed structure in memory.
Key Features of Lookup Collections
1. One-to-Many Mapping
A standard dictionary throws an exception if you attempt to insert
duplicate keys. An ILookup handles multiple identical keys
by design, automatically grouping all items that share the same key into
an internal collection.
2. Immediate Execution
Unlike most LINQ query operators (such as Where,
Select, or GroupBy) that use deferred
execution, ToLookup uses immediate execution. The source
data is completely enumerated and evaluated at the exact point
ToLookup is called.
3. Immutability
The ILookup<TKey, TElement> interface does not
provide Add, Remove, or Clear
methods. Once created, the collection cannot be modified. This makes
lookups inherently thread-safe for concurrent read operations.
4. Safe Indexer Access
When accessing a non-existent key in a standard
Dictionary<TKey, TValue>, the runtime throws a
KeyNotFoundException. In contrast, querying an
ILookup with a key that does not exist safely returns an
empty sequence (Enumerable.Empty<TElement>()),
eliminating the need for ContainsKey checks.
ToLookup vs.
ToDictionary vs. GroupBy
| Feature | ToLookup |
ToDictionary |
GroupBy |
|---|---|---|---|
| Execution | Immediate | Immediate | Deferred |
| Key Multiplicity | One-to-Many | One-to-One (Unique) | One-to-Many |
| Missing Key Access | Returns empty sequence | Throws
KeyNotFoundException |
N/A (Enumeration only) |
| Mutability | Read-Only | Read/Write | Read-Only |
| Key Lookup Indexer | Yes (lookup[key]) |
Yes (dict[key]) |
No |
Practical Code Example
Consider a scenario where you have a list of employees and want to organize them by department for fast querying:
using System;
using System.Collections.Generic;
using System.Linq;
public class Employee
{
public string Name { get; set; }
public string Department { get; set; }
}
public class Program
{
public static void Main()
{
List<Employee> employees = new List<Employee>
{
new Employee { Name = "Alice", Department = "IT" },
new Employee { Name = "Bob", Department = "HR" },
new Employee { Name = "Charlie", Department = "IT" },
new Employee { Name = "Diana", Department = "Marketing" }
};
// Create the lookup
ILookup<string, string> employeesByDept = employees.ToLookup(
e => e.Department, // Key selector
e => e.Name // Element selector
);
// Access elements using the indexer
Console.WriteLine("IT Department:");
foreach (string name in employeesByDept["IT"])
{
Console.WriteLine($"- {name}");
}
// Accessing a key that does not exist returns an empty collection
Console.WriteLine("\nFinance Department Count:");
Console.WriteLine(employeesByDept["Finance"].Count()); // Outputs: 0
// Iterate through all groups
Console.WriteLine("\nAll Departments:");
foreach (IGrouping<string, string> group in employeesByDept)
{
Console.WriteLine($"{group.Key}: {string.Join(", ", group)}");
}
}
}When to Use ToLookup
Use ToLookup when:
- You need to query grouped data repeatedly using index-based lookups
(
lookup[key]). - Your dataset contains multiple elements per key.
- You want to avoid null checks or exceptions when querying keys that might not exist in the collection.
- You need an immutable snapshot of grouped data evaluated immediately.