JavaRush /Курси /C# SELF /Фільтрація даних за допомогою

Фільтрація даних за допомогою Where

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

1. Вступ

Фільтрація — це вибірка з колекції лише тих елементів, які відповідають певній умові. Уявіть: у вашій базі — 10 000 товарів, а менеджеру треба «всього лише» 12, що підходять під новий, хитрий критерій. Писати щоразу вручну перебір і купу перевірок — сумнівне задоволення, а код стає нечитацьким. LINQ бере ці клопоти на себе.

У реальних застосунках фільтрація — найчастіша операція з даними: показуємо користувачеві тільки ті записи, які його цікавлять, «чистимо» колекцію від небажаних елементів, робимо вибірки для звітів, пошуку чи надсилання електронної пошти. На співбесідах питання про LINQ‑фільтрацію трапляються постійно. Розуміння цієї теми — запорука успіху для початківця .NET‑розробника.

2. Знайомимося з методом Where

Сенс і принцип роботи

Метод Where — це метод‑розширення LINQ, який приймає функцію (або лямбда-вираз), що визначає умову фільтрації. Він повертає нову послідовність (не змінюючи початкову!), у якій містяться лише ті елементи, для яких умова повертає true.

Основна сигнатура:

public static IEnumerable<TSource> Where<TSource>(
    this IEnumerable<TSource> source,
    Func<TSource, bool> predicate)

Не лякайтесь generics і «страшних слів». Зараз важливо зрозуміти загальну ідею: Where приймає початкову колекцію та умову, яку ви задаєте. На практиці все просто:

  • source — колекція, яку фільтруємо (наприклад, List<Product>)
  • predicate — функція/умова (наприклад, p => p.Price > 100)

Просте завдання для початку

Припустімо, у нас є список товарів, і ми хочемо вибрати лише ті, у яких ціна більша за 100. У старі добрі часи писали б цикл foreach і вручну додавали потрібні елементи до нового списку. LINQ дозволяє зробити це в один рядок.

3. Фільтрація

Наш клас Product і стартова колекція

У попередньому модулі ми створили приблизно такий клас для товарів:


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

    // Для краси виведення на екран:
    public override string ToString()
    {
        return $"{Name} (ціна: {Price})";
    }
}

Припустімо, у нас є список товарів:


var products = new List<Product>
{
    new Product { Name = "Хліб", Price = 30 },
    new Product { Name = "Молоко", Price = 87 },
    new Product { Name = "Сир", Price = 250 },
    new Product { Name = "Шоколад", Price = 130 }
};

Фільтрація товарів дорожчих за 100 за допомогою Where


var expensiveProducts = products.Where(p => p.Price > 100);

foreach (var product in expensiveProducts)
{
    Console.WriteLine(product);
}

Що відбувається «під капотом»:

  • Where перебирає всі елементи списку products,
  • для кожного викликає функцію (у нашому випадку: p => p.Price > 100),
  • якщо функція повернула true, товар потрапляє в нову колекцію.

Результат на екрані:

Сир (ціна: 250)
Шоколад (ціна: 130)

Зверніть увагу: початковий список products не змінився! LINQ не змінює ваші дані.

4. Лінива фільтрація (Deferred Execution)

Важливий момент: результат роботи Where обчислюється лише тоді, коли ви справді звертаєтесь до елементів. Наприклад, щойно починається цикл foreach, LINQ «йде» по початковій колекції та фільтрує елементи на льоту. Якщо ви не використовуєте результат — нічого не відбувається. Це називається відкладене виконання (deferred execution).

Це дає чимало переваг:

  • Можна будувати довгі ланцюжки методів (наприклад, і відфільтрувати, і відсортувати), а виконання відбудеться лише під час першого фактичного звернення до результату.
  • Економія памʼяті: не створюються зайві проміжні колекції.
  • За потреби можна скасувати виконання або перервати його після потрібної кількості знайдених елементів.

Візуальна схема


Початкова колекція       —►   Where (умова)    —►   Перебираємо тільки потрібні елементи
    
[ х ] [ х ] [ + ] [ + ]  —►   p.Price > 100      —►   [ + ] [ + ]

(х — елемент не задовольняє умові, + — задовольняє)

5. Синтаксичні варіанти

Класичний метод‑розширення (Method Syntax)

Цей стиль ми вже використовуємо:

var result = products.Where(p => p.Price > 100);

Альтернатива: Query Syntax

LINQ підтримує синтаксис, схожий на SQL‑запити:


var result = from p in products
             where p.Price > 100
             select p;

Результат — той самий!
Вибір між синтаксисами — справа смаку, але method syntax (крапка-методи) трапляється частіше, особливо в реальних проєктах і під час побудови довгих ланцюжків.

6. Складні умови фільтрації

Кілька умов

Ви можете використовувати логічні оператори (&&, ||, !):


// Знайти всі дорогі товари, крім "Шоколад"
var filtered = products.Where(
    p => p.Price > 100 && p.Name != "Шоколад"
);

Фільтрація за рядком (підрядок, регістр, Contains)


// Товари, у назві яких є літера "л"
var lProducts = products.Where(p => p.Name.Contains("л"));

Зверніть увагу: Contains чутливий до регістру! Якщо потрібно «без урахування регістру», можна так:


var lProducts = products.Where(p => p.Name
    .ToLower().Contains("л")); // тепер "Молоко" і "Шоколад" обидва потраплять у вибірку

Фільтрація за кількома колекціями

Припустімо, у вас є два списки — товари та платежі. Можна фільтрувати товари, які є у списку платежів, — але для цього краще використовувати методи Any і Join, про які поговоримо далі. Просто знайте: LINQ уміє фільтрувати і складнішими способами!

7. Корисні нюанси

Вкладена фільтрація і ланцюжки методів

Ви можете обʼєднувати кілька етапів фільтрації, викликаючи Where кілька разів підряд:


var filtered = products
    .Where(p => p.Price > 100)
    .Where(p => p.Name.StartsWith("Ш"));

Але краще поєднувати умови в одному Where за допомогою логічних операторів — так ефективніше й код простіший.

Вбудовані засоби порівняння та власні функції

Іноді стандартних операторів бракує. Наприклад, треба фільтрувати рядки без урахування регістру та з урахуванням культури. У такому разі зручно використовувати методи порівняння:


var rusProducts = products.Where(
    p => p.Name.StartsWith("ш", StringComparison.OrdinalIgnoreCase)
);

8. Фільтрація та користувацьке введення

Спробуймо зробити просту консольну фільтрацію на практиці! Наприклад, запитаємо у користувача мінімальну ціну, а потім покажемо всі відповідні товари.


Console.Write("Мінімальна ціна товару? ");
var minPriceStr = Console.ReadLine();
if (double.TryParse(minPriceStr, out double minPrice))
{
    var filtered = products.Where(p => p.Price >= minPrice);
    foreach (var product in filtered)
    {
        Console.WriteLine(product);
    }
}
else
{
    Console.WriteLine("Помилка: введено не число.");
}

Тепер наш «міні-застосунок» уже майже виглядає як магазин!

9. Типові помилки й «граблі» при використанні Where

У програмуванні, як і в житті, трапляються підводні камені. Ось чого варто остерігатися.

Забули викликати ToList() або ToArray()

Результат роботи Where — це не список і навіть не масив! Це обʼєкт IEnumerable<Product>. Найчастіше він чудово працює в циклі foreach, але якщо потрібна саме колекція (наприклад, для індексованого доступу), не забудьте викликати .ToList() або .ToArray():


var filteredList = products.Where(p => p.Price > 100).ToList();

Якщо проігнорувати цей момент, легко отримати помилку типу "Collection was modified during enumeration" — особливо якщо ви вирішите змінити початкову колекцію під час перебору.

Зміна початкової колекції

Оскільки LINQ‑фільтрація відкладена, якщо посеред перебору ви видаляєте елементи з початкового списку, може виникнути виняток. Особливо це стосується багатопотокових сценаріїв або випадків, коли колекція змінюється прямо в процесі фільтрації.

Значення null і предикати

Якщо колекція містить null, а ви в предикаті звертаєтеся до поля обʼєкта без перевірки, отримаєте NullReferenceException. Наприклад:


var filtered = products.Where(p => p.Name.StartsWith("А"));

Якщо якийсь елемент у products дорівнює null, програма завершиться з помилкою.

Рекомендація: завжди додавайте перевірку на null, якщо є ризик наявності таких елементів:


var filtered = products.Where(p => p != null && p.Name.StartsWith("А"));
Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ