Understanding SelectMany and Flattening in C# LINQ
In C# LINQ, SelectMany is a powerful query operator used
to project each element of a sequence to an
IEnumerable<T> and flatten the resulting sequences
into one continuous collection. While a standard Select
maintains the nested hierarchy of data—producing collections within
collections—SelectMany unpacks these child structures into
a single-dimensional list. This article explains the mechanics of
SelectMany, contrasts it with Select, and
demonstrates how to effectively flatten nested arrays, collections, and
parent-child domain models.
What Is a Flattening Operation?
Flattening refers to transforming a hierarchical or multi-dimensional structure (such as a list of lists, or an object containing an inner collection) into a single, linear sequence.
Consider this scenario:
List<List<int>> nestedNumbers = new List<List<int>>
{
new List<int> { 1, 2, 3 },
new List<int> { 4, 5 },
new List<int> { 6, 7, 8 }
};A flattening operation extracts all internal integers and produces a
single sequence: [1, 2, 3, 4, 5, 6, 7, 8].
Select vs.
SelectMany
The core distinction between Select and
SelectMany lies in the mapping relationship:
Select(One-to-One): Maps each source element to a single target element. If the projection returns an enumerable,Selectproduces anIEnumerable<IEnumerable<T>>.SelectMany(One-to-Many): Maps each source element to an enumerable, enumerates through that enumerable, and streams every element directly into a singleIEnumerable<T>.
Example Comparison
var teams = new List<Team>
{
new Team { Name = "Alpha", Members = new[] { "Alice", "Bob" } },
new Team { Name = "Beta", Members = new[] { "Charlie", "Diana" } }
};
// Using Select: Result is IEnumerable<string[]>
var selectResult = teams.Select(t => t.Members);
// Using SelectMany: Result is IEnumerable<string>
var selectManyResult = teams.SelectMany(t => t.Members);selectResult contains two string arrays, whereas
selectManyResult produces a flat list containing
["Alice", "Bob", "Charlie", "Diana"].
How SelectMany Works Internally
Under the hood, SelectMany acts as a nested
foreach loop. It iterates through the outer sequence,
extracts the inner collection for each item, and yields each inner item
individually:
public static IEnumerable<TResult> CustomSelectMany<TSource, TResult>(
this IEnumerable<TSource> source,
Func<TSource, IEnumerable<TResult>> selector)
{
foreach (var parent in source)
{
foreach (var child in selector(parent))
{
yield return child;
}
}
}Because it uses deferred execution (yield return), the
flattening happens on demand as the caller iterates through the
resulting enumerable.
Preserving Parent Context with Result Selectors
A common requirement is to flatten an inner list while retaining
properties from the outer parent object. SelectMany
provides an overload that accepts a second lambda expression—a
result selector—taking both the parent item and the
child item as parameters.
var memberDetails = teams.SelectMany(
team => team.Members,
(team, member) => new
{
TeamName = team.Name,
MemberName = member
}
);This query outputs a flattened list of anonymous objects where each team member is paired with their respective team name:
{ TeamName = "Alpha", MemberName = "Alice" }{ TeamName = "Alpha", MemberName = "Bob" }{ TeamName = "Beta", MemberName = "Charlie" }{ TeamName = "Beta", MemberName = "Diana" }
Flattening with Query Syntax
In LINQ query syntax, SelectMany is represented by using
multiple from clauses. Each subsequent from
clause unwraps a nested collection:
var query = from team in teams
from member in team.Members
select new
{
TeamName = team.Name,
MemberName = member
};The C# compiler automatically translates this query syntax into a
SelectMany method call with a result selector.
Deep Flattening: Multiple Nested Levels
SelectMany can be chained to traverse arbitrarily deep
object hierarchies, such as flattening a list of companies, their
departments, and their employees:
var allEmployees = companies
.SelectMany(c => c.Departments)
.SelectMany(d => d.Employees);Each call strips away one layer of nesting, sequentially resolving
IEnumerable<Company> to
IEnumerable<Department>, and finally to
IEnumerable<Employee>.