JavaRush /Курси /C# SELF /Синтаксис try-catch: обробка винятків

Синтаксис try-catch: обробка винятків

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

1. Вступ

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

Якщо такі проблеми не перехоплювати, програма просто «впаде» — іноді з загадковим повідомленням про помилку і stack trace (стек викликів). Це не те, на що розраховує користувач чи майбутня служба підтримки. Потрібно навчитися перехоплювати такі помилки й реагувати на них: акуратно завершувати роботу, виводити зрозуміле повідомлення або навіть виправляти ситуацію на льоту.

try-catch — це спосіб «сказати» компілятору й середовищу виконання: «Спробуй виконати цей фрагмент коду. Якщо виникне проблема — не панікуй, дай змогу розібратися!»

2. Базовий синтаксис try-catch

Загальна структура дуже проста:


try
{
    // Тут ми пишемо небезпечний (або потенційно небезпечний) код
}
catch (ExceptionType variableName)
{
    // Тут ми пишемо, що робити, якщо виникне виняток типу ExceptionType
}
Базовий синтаксис try-catch
  • Блок try — це «небезпечна зона». У нього кладемо операції, які потенційно можуть «вистрілити».
  • Блок catch перехоплює винятки певного типу. Якщо всередині try виникне виняток, керування одразу перейде до першого відповідного catch, а код після «проблемного місця» у try вже не виконуватиметься.

Приклад: проста обробка винятку

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

int a = 10;
int b = 0;
int result = a / b; // БУМ! System.DivideByZeroException
Console.WriteLine(result);

Програма аварійно завершиться з помилкою DivideByZeroException:

Unhandled exception. System.DivideByZeroException: Attempted to divide by zero.

Тепер «полагодимо» за допомогою try-catch:

int a = 10;
int b = 0;

try
{
    int result = a / b; // небезпечна операція
    Console.WriteLine("Результат ділення: " + result);
}
catch (DivideByZeroException ex)
{
    Console.WriteLine("Ой! Ділення на нуль неможливе: " + ex.Message);
}

Тепер програма не «впаде». У консолі зʼявиться повідомлення про неможливість ділення на нуль:

Ой! Ділення на нуль неможливе: Attempted to divide by zero.

3. Як працює try-catch: крок за кроком

Насправді всередині try може бути скільки завгодно рядків коду. Якщо одна з дій у блоці викидає виняток, керування одразу переходить у найближчий відповідний catch. Усе, що написано після місця помилки в блоці try, виконуватися не буде.

Розгляньмо більший приклад:

try
{
    Console.WriteLine("Починаємо...");
    int[] numbers = { 1, 2, 3 };
    Console.WriteLine(numbers[1]);    // це коректно
    Console.WriteLine(numbers[5]);    // помилка: індекс за межами масиву
    Console.WriteLine("Це повідомлення вже не зʼявиться!");
}
catch (IndexOutOfRangeException)
{
    Console.WriteLine("Помилка: спроба звернення до неіснуючого елемента масиву");
}

Результат у консолі буде таким:

Починаємо...
2
Помилка: спроба звернення до неіснуючого елемента масиву

Process finished with exit code 0.

Як працює ця програма?

  • Виводиться повідомлення «Починаємо…».
  • Далі програма спробує вивести елемент numbers[1] — це 2.
  • Коли доходить до numbers[5], стається «аварія», під час якої викидається IndexOutOfRangeException.
  • Програма одразу перейде у блок catch, пропустивши Console.WriteLine("Це повідомлення вже не зʼявиться!");
  • У консолі зʼявиться наше повідомлення з catch.

4. Чи можна обробляти різні типи помилок по-різному?

Так! Ми можемо вказати кілька блоків catch, щоб по-різному реагувати на різні типи винятків. Це зручно: наприклад, якщо одна дія пов’язана з читанням файлу (може бути FileNotFoundException), а інша — з діленням (може бути DivideByZeroException).

try
{
    // ваш потенційно небезпечний код
}
catch (DivideByZeroException)
{
    Console.WriteLine("Помилка ділення на нуль");
}
catch (IndexOutOfRangeException)
{
    Console.WriteLine("Помилка: індекс поза діапазоном масиву");
}
catch (Exception ex)
{
    Console.WriteLine("Якась непередбачена помилка: " + ex.Message);
}

Останній блок catch з типом Exception — загальний перехоплювач. Він спіймає будь-які інші винятки, що не відповідають попереднім блокам. Але пам’ятайте: якщо розмістите його першим, до інших справа вже не дійде. Завжди ставте «широкі» catch наприкінці.

5. Як виглядає об’єкт винятку?

Всередині блоку catch ми можемо необов’язково вказати змінну — наприклад, catch (Exception ex). Вона містить усю інформацію про помилку: повідомлення, тип помилки, стек викликів, вкладені винятки.

Ось невеликий приклад:

try
{
    string? text = null;
    Console.WriteLine(text.Length); // NullReferenceException
}
catch (NullReferenceException ex)
{
    Console.WriteLine("Перехоплено виняток: " + ex.Message);
    Console.WriteLine("Стек викликів: " + ex.StackTrace);
}

Такий підхід дуже допомагає під час налагодження складних помилок: ви завжди зможете побачити, де й чому програма «спіткнулася».

6. Перехоплюємо помилку й продовжуємо роботу

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

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

bool success = false;
while (!success)
{
    Console.Write("Введіть імʼя файлу: ");
    string fileName = Console.ReadLine() ?? "";

    try
    {
        string content = File.ReadAllText(fileName);
        Console.WriteLine("Файл успішно прочитано!");
        success = true; // Успіх!
    }
    catch (FileNotFoundException)
    {
        Console.WriteLine("Помилка: файл не знайдено. Спробуйте ще раз.");
    }
}

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

7. Типові помилки при роботі з try-catch

Помилка № 1: обгортати весь код в один універсальний try-catch.
Це виглядає спокусливо: один try, один catch (Exception) — і начебто «все захищено». Але насправді ви втрачаєте контроль над ситуацією. Незрозуміло, де саме стався збій і на яку помилку ви фактично реагуєте. У результаті замість надійності — повна втрата діагностики.

Помилка № 2: перехоплювати винятки й нічого з ними не робити.
Приклад з життя:

try
{
    // щось потенційно небезпечне
}
catch
{
    // мовчання...
}
Такий код буквально «каже»: «Щось пішло не так… ну й нехай». Це небезпечно: програма може продовжити роботу в неправильному стані, і ви навіть не дізнаєтеся, що була помилка. Завжди хоча б виведіть повідомлення або запишіть збій у журнал (лог). Жодних порожніх catch!

Помилка № 3: перехоплювати надто загальний тип винятку.
Якщо ви перехоплюєте Exception, хоча знаєте, що може виникнути, скажімо, FormatException або FileNotFoundException, — ви позбавляєте себе можливості належно відреагувати на конкретну проблему. Чим вужчий і точніший ваш catch, тим передбачуваніше й коректніше поводиться програма в нестандартній ситуації.

8. Для чого не рекомендується використовувати винятки

Іноді новачки починають використовувати винятки як звичайний механізм керування логікою виконання, наприклад:

try
{
    if (x < 0) throw new Exception("x має бути >= 0");
}
catch (Exception)
{
    // відновлення стану, продовження роботи…
}

Так робити не варто! Винятки — це інструмент для неочікуваних, рідкісних ситуацій (помилка читання файлу, збій мережі тощо), а не для звичайних перевірок коректності даних користувача. Для таких випадків використовуйте умовні інструкції (if, else) і повертайте спеціальні значення, якщо щось не так.

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