JavaRush /Курси /C# SELF /Множинний catch і фільтри в C#

Множинний catch і фільтри в C#

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

1. Вступ

Коли програма стає трохи складнішою, ніж Hello, world!, ви швидко стикаєтеся із завданням: обробляти помилки різних типів по‑різному. Уявіть: ви працюєте з файлами, мережею, БД — і для різних помилкових сценаріїв реакція має бути різною. Наприклад, якщо файл відсутній — можна запропонувати користувачеві вибрати інший, а при помилці мережі — повторити операцію або показати підбадьорливе повідомлення «Перевірте кабель, можливо, кіт знову його перегриз».

У C# для цього використовують кілька блоків catch підряд. Кожен такий блок спеціалізується на своєму типі винятку (та його нащадках).

Структура множинного catch


try
{
    // Тут — код, який може кинути різні винятки
}
catch (FileNotFoundException ex)
{
    Console.WriteLine($"Файл не знайдено: {ex.Message}");
}
catch (IOException ex) // перехоплює всі помилки введення-виведення, якщо це не FileNotFoundException
{
    Console.WriteLine($"Помилка введення-виведення: {ex.Message}");
}
catch (Exception ex)
{
    Console.WriteLine($"Щось пішло не так: {ex.Message}");
}
Множинні блоки catch для різних типів помилок

Важливо! Компілятор проходить по блоках зверху вниз і вибирає перший, що відповідає типу. Якщо перехоплено FileNotFoundException, далі ланцюжком він уже не піде.

Ілюстрація «ланцюжка»

Тип винятку Який блок перехопить?
FileNotFoundException Перший catch
IOException (інший) Другий catch
ArgumentException Третій (загальний) catch

Чому не можна змішувати?
Найбільш «широку» пастку — наприклад, з catch (Exception) — завжди ставте останньою, інакше вона перехопить усі винятки завчасно, і до спеціалізованих блоків catch виконання не дійде.

Це як вимкнути всі пожежні датчики в будівлі, бо один із них спрацював на підгорілий тостер: у такому разі подальші пожежі система не помітить.

Робочий приклад

using System;
using System.IO;

class Program
{
    static void Main()
    {
        try
        {
            double result = CalculateAverageAgeFromFile("users.txt");
            Console.WriteLine($"Середній вік — {result}");
        }
        catch (FileNotFoundException ex)
        {
            Console.WriteLine("Помилка: файл не знайдено. Перевірте шлях.");
        }
        catch (FormatException ex)
        {
            Console.WriteLine("Помилка даних: не вдалося прочитати вік.");
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Інша помилка: {ex.Message}");
        }
    }

    static double CalculateAverageAgeFromFile(string filePath)
    {
        // (Реалізація: читає файл, обробляє значення віку, обчислює середнє)
        // ...
        throw new NotImplementedException();
    }
}

Тут ми чітко розділяємо, що робити, якщо файлу немає (FileNotFoundException), і що — якщо у файлі некоректні дані (FormatException). Усі інші випадки візьме «запасний» третій блок.

2. Фільтри catch: перехоплюємо тонкі нюанси

Множинні блоки catch зручні, але іноді цього мало. Буває, що всередині одного типу винятку хочеться реагувати по‑різному, залежно від обставин.

Наприклад, якщо мережа не працює, оскільки закінчився інтернет, — ми можемо спробувати повторно під’єднатися. А якщо сервер просто не відповідає — можливо, варто показати інше повідомлення.

Ось тут і стають у пригоді фільтри catch — потужний інструмент C#, який дозволяє перехоплювати не лише за типом, а й за додатковою умовою.

Синтаксис фільтра when


catch (IOException ex) when (ex.Message.Contains("диска немає"))
{
    Console.WriteLine("Упс! Схоже, ви висмикнули флешку.");
}
catch (IOException ex)
{
    Console.WriteLine("Інша помилка введення-виведення: " + ex.Message);
}
Фільтр catch when для уточнення умов перехоплення

Тут перший блок перехоплює лише ті IOException, чиє повідомлення містить фразу "диска немає", — усе інше йде у другий блок.

Застосування в реальному житті

Фільтри особливо корисні під час роботи з мережевими помилками, коли за внутрішніми властивостями винятку треба приймати рішення: повторювати спробу чи просто повідомити про помилку.

Ще приклад: уявіть, у нас у методі, який розбирає файл, ми хочемо не просто загальний FormatException, а окрему гілку — якщо йдеться про вік (наприклад, якщо вік записано як "abc" замість числа).

catch (FormatException ex) when (ex.Message.Contains("вік"))
{
    Console.WriteLine("Помилка: не вдалося прочитати вік. Перевірте дані.");
}
catch (FormatException ex)
{
    Console.WriteLine("Помилка формату даних: " + ex.Message);
}

Фільтри та продуктивність

Фільтри — зручний інструмент, але памʼятайте, що умова у фільтрі обчислюється до входу в блок catch, тобто якщо вона не виконується — тіло блоку взагалі не чіпається.

До речі, якщо фільтр сам кидає виняток (наприклад, у вашому виразі в when стається ділення на нуль), — такий виняток цей блок catch ніколи не перехопить. З цим треба бути обережнішими.

3. Поєднуємо множинний catch і фільтри

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


try
{
    File.AppendAllText("log.txt", "Новий запис\n");
}
catch (IOException ex) when (ex.Message.Contains("Немає місця"))
{
    Console.WriteLine("Помилка: на диску закінчилося місце!");
}
catch (UnauthorizedAccessException)
{
    Console.WriteLine("Помилка: немає прав для запису файлу. Запустіть від імені адміністратора.");
}
catch (IOException ex)
{
    Console.WriteLine("Інша помилка диска: " + ex.Message);
}
Поєднання фільтрів і різних типів catch

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

4. Особливості та «підводні камені» фільтрів і множинних catch

  • Фільтр catch може використовувати змінну винятку (ex), але не може її змінювати.
  • Якщо ви кинете виняток усередині when, він ніяк не перехопиться цим же catch — піде далі вгору стеком викликів, наче фільтра й не було.
  • Логіка фільтра стає частиною контракту: ваш колега по проєкту має знати, що не кожен IOException буде перехоплено — лише якщо умова виконається.
  • Якщо ви використовуєте лише один catch на весь метод, але всередині нього фільтр, то можете взагалі не перехопити «зайві» винятки. Тому, якщо є сумніви — використовуйте явні, окремі блоки.
  • У великих застосунках можна застосовувати фільтри для централізованої логіки, наприклад, логування лише критичних помилок.

5. Сценарії для співбесід і реальної роботи

Знання фільтрів і множинних catch — це не лише про красу чи високі оцінки за clean code. Це реальна навичка, про яку у вас можуть запитати на співбесіді, особливо якщо позиція пов’язана з підтримкою великого, складного, розподіленого застосунку.

Приклади питань:

  • Як у C# обробити різні сценарії помилок читання файлу, щоб для різних ситуацій (немає файлу, на диску немає місця, дані зіпсовані) виводити різні повідомлення?
  • Як не «заглушити» важливу помилку, якщо поставили загальний блок catch (Exception)?
  • Для чого потрібен фільтр catch? Чи може в ньому бути throw?
1
Опитування
Znaĭomstvo z vykliuchennyamy, рівень 13, лекція 4
Недоступний
Znaĭomstvo z vykliuchennyamy
Stek vyklykiv ta stvorennia vlasnykh vykliuchen
Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ