JavaRush /Курсы /C# SELF /Проекция данных с помощью ...

Проекция данных с помощью Select

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

1. Введение

Когда вы смотрите сериал, вы не всегда узнаёте полную информацию обо всех персонажах, только о том, что необходимо для сюжета. В программировании часто возникают похожие ситуации: нам нужен не весь объект целиком, а только отдельные его поля, значения или даже некоторые вычисления на их основе.

Проекция в LINQ — это преобразование элементов исходной последовательности в новую форму. Обычно с помощью метода Select. Можно думать про Select как про волшебную машину, которая берёт каждый элемент, применяет к нему функцию и возвращает результат. Вот и всё, никаких фокусов.

Когда нужна проекция?

  • Хотим вывести только имена пользователей, а не все данные о них.
  • Готовим email-рассылку: из объекта клиента берём только адрес и имя.
  • Делаем расчёт: к цене товара добавляем налог и получаем новую коллекцию.
  • Для UI: нужно отобразить только часть данных, а не перегружать интерфейс.

2. Метод Select: синтаксис и базовая работа

Основной синтаксис

LINQ поддерживает два стиля: методический (Method Syntax) и запросный (Query Syntax). Для Select в обоих стилях результат одинаков.

Метод-синтаксис

var query = myCollection.Select(x => x.ЧтоВернуть);

Здесь x — переменная для каждого элемента коллекции, а справа — выражение, превращающее элемент в то, что нужно.

Запрос-синтаксис

var query = from x in myCollection
            select x.ЧтоВернуть;

Здесь всё немного больше похоже на SQL.

Простой пример: список чисел

var numbers = new List<int> { 1, 2, 3, 4, 5 };
// Хочу получить их квадраты
var squares = numbers.Select(n => n * n);

foreach (var s in squares)
{
    Console.WriteLine(s); // 1, 4, 9, 16, 25
}

Вот только что мы превратили коллекцию чисел в коллекцию их квадратов — мгновенно, не прибегая к ручному циклу!

Пример 2

В прошлых примерах у нас уже был класс Product:

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

Допустим, у нас есть список:

var products = new List<Product>
{
    new Product { Name = "Шоколад", Price = 120 },
    new Product { Name = "Сыр", Price = 250 },
    new Product { Name = "Хлеб", Price = 55 }
};

Если мы хотим просто получить имена всех продуктов, нам не нужен весь объект, правда? Нам нужен только список названий:

var names = products.Select(p => p.Name);
foreach (var name in names)
{
    Console.WriteLine(name); // Шоколад, Сыр, Хлеб
}

3. Возвращаемая коллекция — какие бывают варианты?

Обратите внимание, что Select всегда возвращает коллекцию (точнее — IEnumerable<TOutput>), где TOutput — тип результата функции. Вы сами определяете, что это будет: строка, число, анонимный тип, даже новый объект.

Проекция в новый тип

Например, вы хотите получить не сырой объект, а его "проекцию":

var projections = products.Select(p => new { p.Name, ЦенаСНДС = p.Price * 1.2 });

foreach (var item in projections)
{
    Console.WriteLine($"{item.Name}: {item.ЦенаСНДС}");
}

Здесь мы создали новую коллекцию анонимных объектов — с названием и ценой с учётом воображаемого НДС!

4. Возвращаем новый класс или анонимный тип

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

Анонимные типы

var result = products.Select(p => new { Название = p.Name, СтараяЦена = p.Price, НоваяЦена = p.Price + 40 });
foreach (var p in result)
{
    Console.WriteLine($"{p.Название}: Было {p.СтараяЦена}, стало {p.НоваяЦена}");
}

Анонимные типы удобно использовать для быстрого формирования результатов, особенно когда нет смысла плодить классы ради одной операции.

Используем собственный класс для проекции

Создадим новый класс:

public class ProductDto
{
    public string Name { get; set; }
    public double PriceWithTax { get; set; }
}

Теперь внутри LINQ-запроса:

var productsWithTax = products.Select(p => new ProductDto
{
    Name = p.Name,
    PriceWithTax = p.Price * 1.2
});

foreach (var p in productsWithTax)
{
    Console.WriteLine($"{p.Name}: {p.PriceWithTax}");
}

5. Доступ к внутренним коллекциям и свойствам

Что если внутри объекта есть коллекция? Например, у каждого пользователя есть список заказов.

public class User
{
    public string Name { get; set; }
    public List<Product> Purchases { get; set; }
}

Вы хотите получить список всех списков покупок? Без проблем!

var allPurchases = users.Select(u => u.Purchases);

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

Важно! В этом случае у нас получился не "плоский" список товаров, а коллекция коллекций. Как получить ОДИН список со всеми товарами всех пользователей? Это будет тема следующей лекции — там мы познакомимся с SelectMany.

6. Полезные нюансы

Преобразование к типу коллекции: ToList() и друзья

Select возвращает IEnumerable<T>. Если вы хотите обычный список (List<T>), просто добавьте .ToList():

var nameList = products.Select(p => p.Name).ToList();

Теперь у вас полноценный список, с которым можно работать привычным способом.

Использование индекса элемента

Иногда полезно знать, какой элемент по счёту в коллекции сейчас обрабатывается. У метода Select есть перегрузка:

var indexed = products.Select((product, index) => new { Index = index, Name = product.Name });
foreach (var item in indexed)
{
    Console.WriteLine($"{item.Index}: {item.Name}");
}

Так можно, например, подготавливать данные для пронумерованных списков.

Проекция в программировании

Компетентность в Select — частый вопрос на собеседованиях. Без этой конструкции трудно представить современные приложения:

  • Выборка только нужных данных из БД или внешних сервисов.
  • Подготовка данных для передачи между слоями приложения.
  • Преобразование данных в формате, пригодном для отображения на экране или отправки по сети.
  • Экономия памяти (не тащим весь объект коллекции, а только нужные поля).

Компании, работающие с большими объёмами данных, зачастую строят на этом основную бизнес-логику.

7. Типичные ошибки и особенности

Иногда начинающие разработчики сталкиваются с раздражающими багами — обычно из-за недопонимания, как работает ленивое выполнение (deferred execution) в LINQ. Например, после вызова Select не всегда происходит немедленное выполнение запроса — только когда вы реально итерируете коллекцию (foreach, ToList(), и т.д.). Это позволяет выстроить "трубопровод обработки", но иногда приводит к тому, что результат меняется, если исходные данные были изменены до момента использования результата запроса.

Также не забывайте, что Select не изменяет исходную коллекцию — он строит новую. Поэтому если вы всё ещё ожидаете найти в products новые поля после products.Select(...), этого не произойдёт.

Обработка null: если проекция предполагает обращение к потенциально null-полям, не забывайте об этом позаботиться. В C# 8+ возникает предупреждение компилятора, если возможно получить NullReferenceException.

2
Задача
C# SELF, 31 уровень, 3 лекция
Недоступна
Проекция объектов с вычислением нового значения
Проекция объектов с вычислением нового значения
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ