JavaRush /Курси /C# SELF /Проєкція даних за допомогою

Проєкція даних за допомогою Select

C# SELF
Рівень 31 , Лекція 3
Відкрита

1. Вступ

Коли ви дивитеся серіал, ви не завжди дізнаєтеся усю інформацію про всіх персонажів — лише те, що потрібно для сюжету. У програмуванні часто трапляються схожі ситуації: нам не потрібен увесь об’єкт цілком, а тільки окремі його поля, їхні значення або навіть результати обчислень на їхній основі.

Проєкція у LINQ — це перетворення елементів вихідної послідовності у нову форму, зазвичай за допомогою методу Select. Про Select можна думати як про „чарівну“ машину: вона бере кожен елемент, застосовує до нього функцію і повертає результат. Ось і все — ніяких фокусів.

Коли потрібна проєкція?

  • Потрібно вивести лише імена користувачів, а не всі дані про них.
  • Готуємо електронну розсилку: з об’єкта клієнта беремо лише адресу та ім’я.
  • Робимо розрахунок: до ціни товару додаємо податок і отримуємо нову колекцію.
  • Для 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.

Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ