JavaRush /Курсы /C# SELF /Объединение нескольких источников:

Объединение нескольких источников: SelectMany в LINQ

C# SELF
33 уровень , 2 лекция
Открыта

1. Введение

Вспомним, как работает обычный Select:

var names = users.Select(user => user.Name);

Каждому пользователю сопоставляется его имя — получаем список имён. Всё просто! А если мы хотим получить список всех заказов всех пользователей?

var ordersList = users.Select(user => user.Orders);

Что здесь произойдёт? Для каждого пользователя мы получим список заказов. То есть результатом будет коллекция коллекций (IEnumerable<List<Order>>). Это как полка с коробками: чтобы добраться до каждого заказа, нужно вручную открывать коробку (список заказов пользователя) за коробкой.

Нам хочется весь массив "распаковать", чтобы работать с ним как с обычным списком заказов. Вот тут и приходит на выручку SelectMany.

Аналогия: распаковка коробок

Представьте, что все ваши вещи хранятся не вперемешку, а в ящиках. Каждый ящик — это коллекция, например, покупки одного пользователя. Если вы хотите посмотреть ВСЕ вещи сразу (например, для глобальной ревизии), вы не станете перебирать сначала все ящики, а внутри — вещи, а потом снова и снова. Гораздо удобнее высыпать содержимое всех ящиков в одну большую кучу — и разбирать уже её. Вот так работает SelectMany.

2. Что такое SelectMany?

SelectMany — это LINQ-метод, который берет коллекцию коллекций и превращает её в одну "плоскую" коллекцию. Под "плоской" здесь имеется в виду, что результирующая последовательность больше не будет содержать внутренних списков — все элементы из вложенных коллекций окажутся на одном уровне, как будто их "вытащили" наружу. Никаких матрёшек, просто одна длинная цепочка значений.

Сигнатура метода выглядит так:


IEnumerable<TResult> SelectMany<TSource, TResult>(
    this IEnumerable<TSource> source,
    Func<TSource, IEnumerable<TResult>> selector
)

Работает он просто: у вас есть коллекция (например, список пользователей), и каждый элемент содержит внутри другую коллекцию (например, список заказов этого пользователя). SelectMany берет каждого пользователя, достаёт его заказы и соединяет их в один единый поток заказов — без "обёртки", без вложенности. Именно это и делает его удобным для случаев, когда нужно перейти от "списка списков" к обычному списку.

3. Простые примеры

Пример 1: Список пользователей и их заказы

Начнем с нашей модели приложения, которую мы постепенно развиваем:

// Предположим, что эти классы уже есть с предыдущих уроков
class User
{
    public string Name { get; set; }
    public List<Order> Orders { get; set; }
}

class Order
{
    public int Id { get; set; }
    public string ProductName { get; set; }
}

Допустим, есть список пользователей:

var users = new List<User>
{
    new User
    {
        Name = "Иван",
        Orders = new List<Order>
        {
            new Order { Id = 1, ProductName = "Книга" },
            new Order { Id = 2, ProductName = "Ручка" }
        }
    },
    new User
    {
        Name = "Мария",
        Orders = new List<Order>
        {
            new Order { Id = 3, ProductName = "Ноутбук" }
        }
    }
};

Задача: получить список всех товаров, которые когда-либо были куплены всеми пользователями.

Обычный Select:

var ordersPerUser = users.Select(user => user.Orders);
// IEnumerable<List<Order>>

Тут на выходе у нас множество "коробок", в каждой — список заказов по пользователю.

SelectMany:

var allOrders = users.SelectMany(user => user.Orders);
// IEnumerable<Order>

Теперь мы получаем одну "длинную" коллекцию всех заказов!

Визуализация

Пользователь Заказы
Иван Книга, Ручка
Мария Ноутбук
  • После Select: [[Книга, Ручка], [Ноутбук]] — список списков
  • После SelectMany: [Книга, Ручка, Ноутбук] — единый список

Пример 2: Работа с несколькими уровнями вложенности

Если у нас, например, у заказа есть список товаров (корзина), тогда SelectMany можно применять несколько раз — по аналогии с матрёшкой!

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

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

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

var users = new List<User>
{
    new User
    {
        Name = "Петр",
        Orders = new List<Order>
        {
            new Order
            {
                Id = 10,
                Products = new List<Product>
                {
                    new Product { Name = "Телефон" },
                    new Product { Name = "Зарядка" }
                }
            }
        }
    }
};

Чтобы получить все продукты всех заказов всех пользователей, используем двойной SelectMany:

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

Таким способом вы раскрываете сначала один уровень "коробок", потом следующий.

Пример 3: Использование SelectMany в Query Syntax

Если вам больше по душе SQL-подобный синтаксис LINQ (Query Syntax), то SelectMany выражается через несколько from подряд:

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

Здесь каждый следующий from берёт элемент из коллекции, возвращаемой предыдущим.


users
  └─ user1
        └─ order1 
             └─ product1, product2
        └─ order2
             └─ product3
  └─ user2
        └─ order3
             └─ product4
Схема вложенных коллекций для SelectMany

Query Syntax пробегает по всем путям и возвращает один длинный список продуктов.

4. Варианты использования и тонкости

SelectMany для преобразования и фильтрации

В SelectMany можно применять не только простое раскрытие, но и одновременную фильтрацию или проекцию элементов. Например, выбрать только продукты дороже 1000 евро из всех заказов:

var expensiveProducts = users
    .SelectMany(u => u.Orders)
    .SelectMany(o => o.Products)
    .Where(p => p.Price > 1000);

Или — с небольшим вложением логики прямо внутрь SelectMany (через дополнительный селектор):

var expensiveProducts = users
    .SelectMany(u => u.Orders.SelectMany(o => o.Products))
    .Where(p => p.Price > 1000);

Это вопрос вкуса: оба варианта делят проблему на отдельные последовательные шаги.

Использование перегрузки SelectMany с проекцией

Дополнительная "продвинутая" перегрузка метода позволяет не только раскрывать, но и сразу формировать результат. Например: получить пары "Имя пользователя — Имя продукта":

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

Как это работает? Первый параметр — user => user.Orders.SelectMany(order => order.Products) — раскрывает все продукты пользователя, второй — комбинирует исходный объект пользователя и продукт внутри вложенного enumerator-а.

5. Важные моменты и частые ошибки

Можно столкнуться с ситуацией, когда забыли добавить SelectMany и вместо плоской последовательности получили массив "коробок". Например, когда применили обычный Select, а не SelectMany там, где нужно раскрыть сразу все элементы. В результате нельзя перебрать или обработать элементы напрямую.

Очень легко запутаться, если коллекции вложены, особенно при большом уровне вложенности. Возьмите за правило: если на выходе нужен не список списков, а плоский список, — смело используйте SelectMany.

Где пригодится на практике?

В любой задаче, где у вас есть объект-коллекция, у каждого элемента которой — своя коллекция (например, у "пользователь — заказы", "курс — студенты", "новость — комментарии"), вы будете возвращаться к SelectMany. На собеседованиях это одна из "любимых" тем: не все начинающие отличают Select от SelectMany и могут корректно раскрыть коллекции.

В реальных проектах это позволяет делать красивый, лаконичный и быстрый код без лишнего уровня вложенности. На платформе .NET это стандартный и оптимизированный путь для обработки "слоеных" коллекций.

Разница между Select и SelectMany

Метод Select сохраняет структуру: если вы применяете его к коллекции пользователей, у каждого из которых есть список заказов, то результатом будет коллекция коллекций — например, [[A, B], [C]]. Это удобно, если вы хотите дальше работать с данными по группам (например, по пользователям).

А вот SelectMany делает «сплющивание» — объединяет все вложенные коллекции в одну общую последовательность: [A, B, C]. Это полезно, когда вам не важна группировка, и вы хотите работать со всеми внутренними элементами как с единой массой — например, найти все продукты, независимо от того, к какому пользователю или заказу они относятся.


+---------------------+         +--------------------+
| users               |         | allOrders          |
+---------------------+         +--------------------+
| Иван: [A, B]        | --+   +-> Order A           |
| Мария: [C]          | --+ | +-> Order B           |
+---------------------+   | | +-> Order C           |
                          | | +--------------------+
     Select:              | |
   [[A, B], [C]] <--------+ +
                          |
     SelectMany: <--------+
   [A, B, C]
Схема разницы между Select и SelectMany

Грубо говоря:

  • Select → «дай мне списки заказов по пользователям»;
  • SelectMany → «дай мне просто все заказы».
2
Задача
C# SELF, 33 уровень, 2 лекция
Недоступна
Использование SelectMany для объединения коллекций
Использование SelectMany для объединения коллекций
Комментарии (2)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
Александр Уровень 34
10 февраля 2026
задача странная. проще объединить все коллекции через .Concat вместо запихивания трёх коллекций в одну. А потом уже хоть сразу строку через .Join делай, или через .Distinct() отсей дубликаты (если надо)
Ильнур Уровень 66
28 октября 2025

var userProductPairs = users.SelectMany(
    user => user.Orders.SelectMany(order => order.Products),
    (user, product) => new { user.Name, product.Name }
);
Здесь нужно в анонимном типе хотя бы одному из свойств изменить имя, иначе происходит конфликт имен