← Назад к вопросам

Что возвращает SelectMany после Where?

2.0 Middle🔥 142 комментариев
#C# и ООП#Коллекции и структуры данных

Комментарии (2)

🐱
deepseek-v3.2PrepBro AI6 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Ответ на вопрос о SelectMany после Where

Короткий ответ: Метод SelectMany, вызванный после Where, возвращает одномерную коллекцию «развёрнутых» элементов, полученную из отфильтрованной исходной последовательности. Сначала Where фильтрует элементы, затем SelectMany «уплощает» (flattens) каждый отфильтрованный элемент, который сам является коллекцией, в последовательность отдельных объектов, объединяя их в один общий поток.


Подробное объяснение с примером

Рассмотрим типичный сценарий: у нас есть коллекция пользователей, у каждого из которых есть список заказов. Мы хотим сначала отфильтровать активных пользователей, а затем получить все заказы от этих пользователей в виде единого плоского списка.

// Модели данных
public class User
{
    public string Name { get; set; }
    public bool IsActive { get; set; }
    public List<Order> Orders { get; set; }
}

public class Order
{
    public int Id { get; set; }
    public decimal Amount { get; set; }
}

// Исходные данные
var users = new List<User>
{
    new User { Name = "Alice", IsActive = true, Orders = new List<Order> { new Order { Id = 1, Amount = 100 }, new Order { Id = 2, Amount = 200 } } },
    new User { Name = "Bob", IsActive = false, Orders = new List<Order> { new Order { Id = 3, Amount = 150 } } },
    new User { Name = "Charlie", IsActive = true, Orders = new List<Order> { new Order { Id = 4, Amount = 300 } } }
};

// Цепочка Where -> SelectMany
var allOrdersFromActiveUsers = users
    .Where(u => u.IsActive)           // Фильтрация: остаются Alice и Charlie
    .SelectMany(u => u.Orders);       // "Разворачивание": из Alice берём заказы 1 и 2, из Charlie — заказ 4

// Результат: коллекция из трёх заказов: Order{Id=1}, Order{Id=2}, Order{Id=4}
foreach (var order in allOrdersFromActiveUsers)
{
    Console.WriteLine($"Order ID: {order.Id}, Amount: {order.Amount}");
}

Как это работает шаг за шагом:

  1. Where(u => u.IsActive):
    *   Принимает исходную коллекцию `users`.
    *   Применяет предикат `u => u.IsActive` к каждому элементу.
    *   Возвращает новую последовательность (тип `IEnumerable<User>`), содержащую только пользователей, удовлетворяющих условию (Alice и Charlie). Пользователь Bob исключается.

  1. SelectMany(u => u.Orders):
    *   Принимает **отфильтрованную** последовательность `User` от `Where`.
    *   Для каждого пользователя (Alice и Charlie) вызывает *функцию-селектор* `u => u.Orders`. Эта функция должна возвращать **коллекцию** (`IEnumerable<Order>`). В данном случае это свойство `List<Order>`.
    *   `SelectMany` берет все эти внутренние коллекции (`Orders` у Alice и `Orders` у Charlie) и **объединяет их элементы в одну общую одномерную последовательность** типа `IEnumerable<Order>`.


Важные нюансы

  • Тип возвращаемого значения определяется функцией-селектором внутри SelectMany. В примере выше мы получили IEnumerable<Order>. Если бы мы «разворачивали» коллекции строк, результат был бы IEnumerable<string>.
  • Перегрузки SelectMany: Метод имеет несколько перегрузок, позволяющих, например, сохранить ссылку на исходный элемент в результирующей проекции:
    var result = users
        .Where(u => u.IsActive)
        .SelectMany(
            user => user.Orders,          // Коллекция для "разворачивания"
            (user, order) => new { UserName = user.Name, OrderId = order.Id } // Проекция результата
        );
    // Результат: анонимные объекты { UserName = "Alice", OrderId = 1 }, { UserName = "Alice", OrderId = 2 }...
    
  • Отличие от Select: Если бы вместо SelectMany использовали Select, мы бы получили не плоский список заказов, а коллекцию списков заказов:
    var listOfLists = users.Where(u => u.IsActive).Select(u => u.Orders);
    // Тип: IEnumerable<List<Order>>. Это два отдельных списка: список заказов Alice и список заказов Charlie.
    
    `SelectMany` решает задачу **уплощения** (flattening) такой вложенной структуры.

Заключение

Таким образом, комбинация Where -> SelectMany является мощным и часто используемым паттерном в LINQ для:

  1. Фильтрации исходных объектов по условию.
  2. Извлечения и объединения вложенных коллекций из этих отфильтрованных объектов в единый, удобный для последующей обработки поток данных. Это ключевой инструмент для работы с иерархическими или связанными данными.