CodeGym /Courses /C# SELF /Flattening collections with

Flattening collections with SelectMany

C# SELF
Level 31 , Lesson 4
Available

1. Introduction

When you're working with just a "flat" list of objects (like our list of products or users from previous lectures), it's pretty straightforward: filter, transform, sort. But in real business apps, at interviews (and even in homework!), you often run into collections that contain other collections inside.

For example, there's a User class that has a list of orders. Or, say, a Product class with a list of reviews. Now here's the challenge: how do you get a list of all orders from all users? Or collect all reviews for all products? And what if you need a list of all products in all orders?

In these situations, regular Select and even C#'s magic sometimes just isn't enough. That's where our hero comes in — the SelectMany operator, turning a "list of lists" into just a "list".

Kitchen analogy

Imagine you have a box, with more boxes inside, and in those boxes — cookies. Your job is to dump all the cookies into one big bowl. You wouldn't take each box and pour out one cookie at a time, you'd just grab all the boxes and all the cookies — right into the bowl.

That's exactly what SelectMany does: it "flattens" the collection, turning a "box of boxes" into one "bowl" with all the goodies.

2. Let's recall our app example

In previous lectures, we built a simple app to work with users and products. For this lecture, let's add some "nested" collections.

Suppose we have these classes:


public class User
{
    public string Name { get; set; }
    public List<Order> Orders { get; set; }
}

public class Order
{
    public int Id { get; set; }
    public List<Product> Products { get; set; }
}

public class Product
{
    public string Name { get; set; }
}

Now let's say we have a list of users, each with a list of orders, and each order has a list of products.

How do you get all products from all orders of all users?

Suppose we want to make a master list of all products — no nesting, just one big "bowl". What if we try regular Select?


List<User> users = ...; // let's say we already have the data somewhere

var allOrders = users.Select(u => u.Orders);

The type of allOrders will be... IEnumerable<List<Order>>. We got a "list of lists", meaning a bunch of order collections. What we want is one flat list of all orders.

Let's try again:


var allProducts = users.Select(u => u.Orders)
                       .Select(oList => oList.Select(o => o.Products));

What now? Now we've got a "list of lists of lists"! We're just making the structure more complicated, not simpler.

3. The solution: SelectMany operator

Here's where the real power of SelectMany comes in. Its job is to "flatten" nested collections into one.

General syntax:


collection.SelectMany(item => item.InnerCollection)

In our case:


var allOrders = users.SelectMany(u => u.Orders);
// Type: IEnumerable<Order>

Now we've just got a sequence of all orders from all users.

Let's go even deeper! All products from all orders of all users:


var allProducts = users
    .SelectMany(u => u.Orders)
    .SelectMany(o => o.Products);
// Type: IEnumerable<Product>

What about in one line?

Yep, you can do that too:


var allProducts = users.SelectMany(u => u.Orders.SelectMany(o => o.Products));

It's better to read this chain in parts, especially if you're not a robot.

4. Let's break it down with a big example

Let's create some test data and see how SelectMany works in action.


var users = new List<User>
{
    new User
    {
        Name = "Alice",
        Orders = new List<Order>
        {
            new Order
            {
                Id = 1,
                Products = new List<Product>
                {
                    new Product { Name = "Cookie" },
                    new Product { Name = "Milk" },
                }
            },
            new Order
            {
                Id = 2,
                Products = new List<Product>
                {
                    new Product { Name = "Chocolate" }
                }
            }
        }
    },
    new User
    {
        Name = "Bob",
        Orders = new List<Order>
        {
            new Order
            {
                Id = 3,
                Products = new List<Product>
                {
                    new Product { Name = "Coffee" },
                    new Product { Name = "Tea" }
                }
            }
        }
    }
};

"Dumping" all products into one bowl


var allProducts = users
    .SelectMany(u => u.Orders)
    .SelectMany(o => o.Products);

foreach (var product in allProducts)
{
    Console.WriteLine(product.Name);
}

Program output:

Cookie
Milk
Chocolate
Coffee
Tea

What's happening at each step?

  1. users.SelectMany(u => u.Orders) — we take all users and "unroll" their orders into one collection of orders (now we've got 3 orders).
  2. .SelectMany(o => o.Products) — unroll the list of products from all orders.

Heads up: If we used regular Select, we'd get a collection of collections (like IEnumerable<List<Product>>). But with SelectMany we get a "flat" collection of products: IEnumerable<Product>.

5. What it looks like in a diagram

Before SelectMany


users (List<User>)
   └── User #1
   │      └── Orders (List<Order>)
   │           ├── Order #1 -> Products (List<Product>)
   │           └── Order #2 -> Products (List<Product>)
   └── User #2
          └── Orders (List<Order>)
               └── Order #3 -> Products (List<Product>)

After the first SelectMany(u => u.Orders)


IEnumerable<Order>: [Order #1, Order #2, Order #3]

After the second SelectMany(o => o.Products)


IEnumerable<Product>: [Cookie, Milk, Chocolate, Coffee, Tea]

6. Comparison: Select vs SelectMany

Select SelectMany
Returns Collection of "collections" Flat collection
Example
users.Select(u => u.Orders)
IEnumerable<List<Order>>
users.SelectMany(u => u.Orders)
IEnumerable<Order>
Use case If you want to keep the nested structure If you want one flat sequence

Table: when to use which operator

What you want to do What to use Result
Get a list of lists (don't flatten) Select
IEnumerable<ICollection<T>>
Get one "flat" sequence SelectMany
IEnumerable<T>
Get parent-child pairs SelectMany with result selector
IEnumerable<CustomType>
Just transform elements of a "flat" collection Select
IEnumerable<TransformedType>

7. Handy tips

Query Syntax (for fans of "SQL-style")

For those who love expressiveness — LINQ lets you write queries in a syntax that looks like SQL:


var allProducts = from user in users
                  from order in user.Orders
                  from product in order.Products
                  select product;

This code does the same thing as the SelectMany chain, just looks like a multi-level "from".

"Flattening" a matrix

A classic use case for SelectMany is working with a two-dimensional array or a list of lists.


List<List<int>> matrix = new List<List<int>>
{
    new List<int> { 1, 2, 3 },
    new List<int> { 4, 5 },
    new List<int> { 6 }
};

How do you get one list of all the numbers?


var flat = matrix.SelectMany(row => row);
foreach (var value in flat)
{
    Console.Write(value + " "); // 1 2 3 4 5 6
}

8. Advanced syntax

Sometimes, using an overload of SelectMany, you can return not just the inner collection's element, but also info from the outer one.

Here's an example: you need to get pairs "User name — Product name" for every product in every order.


var userProductPairs = users.SelectMany(
    user => user.Orders.SelectMany(
        order => order.Products,
        (order, product) => new { UserName = user.Name, ProductName = product.Name }
    )
);

foreach (var pair in userProductPairs)
{
    Console.WriteLine($"{pair.UserName} ordered {pair.ProductName}");
}

Here, the second lambda parameter is the outer collection's element. Super useful so you don't "lose" info from the outer levels.

9. Practical scenarios

Lots of orders, lots of products

You're a dev for an online store. Want to count how many unique products all clients bought? Here you go:


var uniqueProductNames = users
    .SelectMany(user => user.Orders)
    .SelectMany(order => order.Products)
    .Select(product => product.Name)
    .Distinct();

foreach (var name in uniqueProductNames)
{
    Console.WriteLine(name);
}

Building a flat collection from a hierarchy

You've got a list of company departments, each with employees. Want a list of all employees in the company:


var allEmployees = departments.SelectMany(d => d.Employees);

Working with strings: "flattening" an array of words into characters


string[] words = { "hello", "world" };
var allChars = words.SelectMany(w => w.ToCharArray());
foreach (var c in allChars)
{
    Console.Write(c + " "); // h e l l o w o r l d
}

10. Common mistakes and gotchas

Now that you're pumped about the power of SelectMany, it's time for a few common mistakes to watch out for.

The most popular one — expecting Select to give you a "flat" collection. In reality, it always returns a collection of collections if your inner function returns a collection. So you end up writing double loops or getting lost in types.

Second mistake — not noticing that "flattening" makes you lose info about the outer element: for example, if you're only working with products, you won't know which order or user it came from unless you use the overload with a result selector.

Third gotcha — if your inner lists might be null, add some protection before using SelectMany: either filter or replace with an empty list, otherwise you'll get a NullReferenceException faster than you can hit F5.

2
Task
C# SELF, level 31, lesson 4
Locked
Extracting all items from nested lists
Extracting all items from nested lists
1
Survey/quiz
Introduction to LINQ, level 31, lesson 4
Unavailable
Introduction to LINQ
LINQ Basics: Simple Queries and Data Filtering
Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION