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, Select produces an IEnumerable<IEnumerable<T>>.
  • SelectMany (One-to-Many): Maps each source element to an enumerable, enumerates through that enumerable, and streams every element directly into a single IEnumerable<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>.