1. Вступ
В ідеальному світі й код був би бездоганним. Та, на жаль, навіть найакуратніший програміст рано чи пізно зіткнеться з неочікуваними ситуаціями: не знайдено файл, користувач ввів рядок замість числа, мережа раптово перестала працювати або з бази даних повернулося щось не так, як очікувалося.
Якщо такі проблеми не перехоплювати, програма просто «впаде» — іноді з загадковим повідомленням про помилку і stack trace (стек викликів). Це не те, на що розраховує користувач чи майбутня служба підтримки. Потрібно навчитися перехоплювати такі помилки й реагувати на них: акуратно завершувати роботу, виводити зрозуміле повідомлення або навіть виправляти ситуацію на льоту.
try-catch — це спосіб «сказати» компілятору й середовищу виконання: «Спробуй виконати цей фрагмент коду. Якщо виникне проблема — не панікуй, дай змогу розібратися!»
2. Базовий синтаксис try-catch
Загальна структура дуже проста:
try
{
// Тут ми пишемо небезпечний (або потенційно небезпечний) код
}
catch (ExceptionType variableName)
{
// Тут ми пишемо, що робити, якщо виникне виняток типу ExceptionType
}
- Блок 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) і повертайте спеціальні значення, якщо щось не так.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ