1. Вступ
Уявіть, що ви приходите до бібліотеки й маєте знайти всі книжки з програмування, видані після 2020 року, відсортовані за імʼям автора. Навряд чи ви бігатимете від полиці до полиці та перевірятимете кожну книжку вручну, правда? Ви б попросили бібліотекаря, який знає, як швидко знайти те, що вам потрібно.
LINQ (Language Integrated Query, або «Запити, інтегровані в мову») — це наш «розумний бібліотекар», «SQL‑рушій» (тобто інструмент для написання запитів до даних) просто в C#!
Думайте про LINQ як про мову, що дозволяє вам описувати, що ви хочете отримати з даних, а не як це зробити. Замість того щоб вимагати: «Візьми перший елемент, перевір умову, якщо підходить — додай у новий список, потім переходь до другого…», ви просто кажете: «Дайте мені всі продукти, ціна яких більша за 1000». А C# сам розбереться, як це виконати найефективніше.
Ключова ідея LINQ: надати стандартний синтаксис для запитів до будь‑яких джерел даних, які реалізують інтерфейс IEnumerable<T>. І це зручно, адже List<T>, масиви, HashSet<T> — усі вони реалізують IEnumerable<T>. А якщо ваші дані в базі, то спеціальні бібліотеки (наприклад, Entity Framework) перетворюють ваші LINQ‑запити на справжній SQL!
LINQ з’явився у C# понад 10 років тому. Це була знакова подія, що змінила підхід до роботи з даними в .NET. До LINQ розробникам доводилося писати багато шаблонного коду для фільтрації, сортування та трансформації колекцій. Або використовувати рядки SQL‑запитів, а вони не перевірялися компілятором і були схильні до помилок під час виконання. LINQ приніс концепцію «запитів» прямо в мову програмування, зробивши їх типобезпечними та читабельнішими.
Багато хто вважає, що LINQ — це одне з найзначніших нововведень у C# з часів появи узагальнень (Generics).
2. Переваги LINQ: Чому його всі люблять?
Стислість і читабельність коду: Щоб відфільтрувати, наприклад, продукти з ціною понад 1000 у великій базі даних, раніше доводилося писати кілька рядків коду. Завдяки LINQ це можна виразити одним рядком. Менше коду — менше потенційних багів, вища читабельність і зрозумілість. Код стає ближчим до природної мови.
Жарт програміста: «Чим менше коду я пишу, тим менше багів можу туди засунути». LINQ справді в цьому допомагає!
Потужність і гнучкість: LINQ дає багатий набір операцій: фільтрація (Where), проєкція (Select), сортування (OrderBy), групування (GroupBy), агрегація (Sum, Average), об’єднання (Join) та багато іншого. Можна вирішувати складні завдання оброблення даних, комбінуючи ці операції.
Типобезпечність: Це дуже важливо! Коли ви пишете SQL‑запит у вигляді рядка, компілятор нічого про нього не знає. Якщо ви помилитеся в назві стовпця — дізнаєтеся про це лише під час виконання програми, коли вона завершиться з помилкою. З LINQ компілятор C# перевіряє ваш запит на помилки вже під час компіляції. Якщо ви звернетеся до неіснуючого поля об’єкта Product, компілятор одразу покаже помилку. Це як мати персонального коректора, який ловить ваші опечатки ще до того, як їх побачать інші.
Інтеграція з мовою: Запити LINQ — це не якісь «магічні» рядки. Це повноцінні конструкції C#, які використовують знайомі вам лямбда‑вирази та працюють із уже знайомими типами даних. Перехід до LINQ дуже плавний.
Відкладене виконання (Deferred Execution): Це одна з найкрутіших і, можливо, найскладніших концепцій LINQ, але дуже важлива. Суть у тому, що LINQ‑запит не виконується одразу, як тільки ви його написали. Він «збирається» і чекає, поки ви реально не попросите результат (наприклад, почнете перебирати його в foreach). Це дозволяє оптимізувати запити, особливо під час роботи з великими обсягами даних. Ми поговоримо про це детальніше у лекції 166. Поки просто майте на увазі: LINQ розумний — він не робить зайвої роботи.
Універсальність і розширюваність: LINQ — це не тільки для списків у пам’яті. Є різні «провайдери» LINQ:
- LINQ to Objects: для колекцій у пам’яті (List<T>, масиви тощо).
- LINQ to SQL / Entity Framework Core: для SQL‑баз даних (ваші LINQ‑запити перетворюються на SQL‑запити).
- LINQ to XML: для роботи з XML‑документами.
- І багато інших.
Це означає, що, опанувавши LINQ, ви зможете працювати з даними з різних джерел, використовуючи ту саму логіку запитів.
3. Проблема роботи з колекціями «по‑старому»
До появи LINQ код обробки колекцій у C# виглядав приблизно так:
var products = new List<Product> { /* ... */ };
var expensive = new List<Product>();
foreach (var prod in products)
{
if (prod.Price > 100)
expensive.Add(prod);
}
expensive.Sort((a, b) => a.Price.CompareTo(b.Price));
foreach (var item in expensive)
Console.WriteLine(item.Name);
Такий підхід типовий: спочатку вручну відфільтрувати, потім відсортувати — обов’язково створити проміжний список. Чим більше логіки, тим більше коду, багів і змінних. Усе це нагадує майстерню зі збирання меблів: деталі є, але процес збирання — ручний і виснажливий.
Аліса в країні циклів
Уявіть, що у вас є величезний список користувачів, і ви хочете отримати тільки імена тих, хто старший за 18 і живе у місті Неонвіль, відсортувати їх за абеткою та вивести три перші. Доведеться писати вкладені цикли, умови, сортування, допоміжні списки… або просто скористатися LINQ.
4. Як виглядає LINQ‑запит? Прості приклади
LINQ дає два основні варіанти синтаксису:
- Method Syntax (ланцюжок методів)
- Query Syntax (мовоподібний SQL)
Найчастіше використовують Method Syntax, особливо в сучасних проєктах. Ось приклад на базі нашого застосунку:
var expensive = products
.Where(p => p.Price > 100)
.OrderBy(p => p.Price)
.Select(p => p.Name)
.Take(3);
foreach (var name in expensive)
Console.WriteLine(name);
Усе наочно: фільтр, сортування, вибір конкретного поля, обмеження кількості. Мінімум коду — максимум сенсу.
Те саме через Query Syntax
var expensive = from p in products
where p.Price > 100
orderby p.Price
select p.Name;
foreach (var name in expensive.Take(3))
Console.WriteLine(name);
Обидва варіанти дають той самий результат — обирайте той, що більше до душі (але method‑синтаксис усе ж частіше використовують).
5. Архітектура LINQ: що під капотом?
LINQ працює з будь‑якими колекціями, які реалізують інтерфейс IEnumerable<T> (або IQueryable<T>, про нього поговоримо згодом).
Основні компоненти LINQ
| Компонент | Короткий опис |
|---|---|
| Методи розширення LINQ | Статичні методи (, , тощо) у класі |
| Делегати | Найчастіше використовують і |
| Відкладене виконання | Запит обчислюється лише під час першого перебирання (наприклад, через ) |
| Query Provider | (для LINQ to SQL, LINQ to Entities) — перетворює ланцюжок методів на SQL‑запити тощо. |
Візуальна схема
Колекція (List<Product>, T[], ...)
│
▼
LINQ-методи (Where, OrderBy, Select...)
│
▼
Запит (IEnumerable<T>)
│
▼
Реальне виконання (foreach, ToList, Count, ...)
6. Приклад: покроковий розбір LINQ‑ланцюжка у нашому застосунку
Повертаємося до нашого міні‑застосунку з класом Product:
// Клас продукту
class Product
{
public string Name { get; set; }
public double Price { get; set; }
}
Ось набір продуктів:
var products = new List<Product>
{
new Product { Name = "Сир", Price = 250.5 },
new Product { Name = "Хліб", Price = 30 },
new Product { Name = "Молоко", Price = 80 },
new Product { Name = "Кава", Price = 330 },
new Product { Name = "Масло", Price = 140 }
};
Припустімо, наше завдання таке: вивести на екран назви всіх товарів, дорожчих за 100 євро, та відсортувати за ціною.
Старий спосіб
var filtered = new List<Product>();
foreach (var p in products)
{
if (p.Price > 100)
filtered.Add(p);
}
filtered.Sort((a, b) => a.Price.CompareTo(b.Price));
foreach (var p in filtered)
Console.WriteLine(p.Name);
LINQ-спосіб
var expensive = products
.Where(p => p.Price > 100)
.OrderBy(p => p.Price)
.Select(p => p.Name);
foreach (var name in expensive)
Console.WriteLine(name);
У першому рядку ми описуємо саму суть завдання: «Відфільтруй ті об’єкти, у яких Price > 100, відсортуй за ціною, вибери імена».
7. Часті операції LINQ та їх аналоги «по‑старому»
| Операція | Логіка «по‑старому» | LINQ |
|---|---|---|
| Фільтрація | foreach + if + Add | |
| Проєкція (вибір поля) | foreach + Add(field) | |
| Сортування | Sort(comparer) | , |
| Унікальні значення | foreach + Contains + Add | |
| Підрахунок | foreach + counter++ | , |
| Перевірка умови | foreach + if | , |
| Пошук першого/останнього | foreach + if + break | , , |
| Обмеження кількості | foreach + лічильник + break | , |
8. Практика: додаємо LINQ у застосунок
Візьмемо базовий код нашого застосунку (список Product) і спробуємо реалізувати кілька корисних операцій за допомогою LINQ.
Фільтрація та сортування
// Вибрати продукти дешевші за 200 євро та відсортувати за іменем
var cheapProducts = products
.Where(p => p.Price < 200)
.OrderBy(p => p.Name);
foreach (var p in cheapProducts)
Console.WriteLine($"{p.Name}: {p.Price} євро");
Перетворення (проєкція) колекції
// Отримати список цін (double)
var prices = products.Select(p => p.Price);
foreach (var price in prices)
Console.WriteLine(price);
Пошук першого підхожого елемента
// Перший продукт, назва якого починається на "К"
var firstK = products.FirstOrDefault(p => p.Name.StartsWith("К"));
Console.WriteLine(firstK?.Name ?? "Не знайдено");
Підрахунок кількості товарів дорожчих за 100 євро
int count = products.Count(p => p.Price > 100);
Console.WriteLine($"Таких товарів: {count} шт.");
Починаючи з цієї лекції, ми активно використовуватимемо LINQ для оброблення колекцій, фільтрування, вибору потрібних даних, агрегацій та багато чого ще. Нові методи й можливості LINQ (зокрема новинки .NET 9) розглянемо в найближчих лекціях, а поки — не соромтеся експериментувати з простими запитами, щоб відчути всю силу цього інструменту!
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ