Підрозділ 5.1
Конструкція try..catch..finally
Пояснює конструкцію try..catch..finally, порядок виконання блоків, аварійне завершення програми та випадки, коли краще використати умовну перевірку.
5.1. Конструкція try..catch..finally
У реальних програмах завжди є ситуації, які важко або неможливо повністю передбачити під час написання коду. Файл може не існувати, мережа може обірватися, користувач може ввести не число, а текст. Такі позаштатні події під час виконання програми називаються винятками (exceptions).
У C# передбачено спеціальний механізм обробки винятків — конструкція try...catch...finally. Вона дозволяє виявити аварійну ситуацію, обробити її або зробити необхідне завершення ресурсів — і продовжити виконання програми замість аварійного завершення.
Що таке виняток
Виняток — це об'єкт, який середовище виконання CLR (Common Language Runtime) автоматично створює, коли відбувається помилка, і «викидає» вгору по стеку викликів у пошуках обробника. Якщо обробника немає — програма аварійно завершується.
Кожен виняток є екземпляром класу, що успадковується від System.Exception. Наприклад:
DivideByZeroException— ділення цілого числа на нульFormatException— некоректний формат рядка при конвертаціїNullReferenceException— звернення доnull-посиланняIndexOutOfRangeException— вихід за межі масиву
Структура try...catch...finally
Загальна форма конструкції:
try
{
// код, в якому може виникнути виняток
}
catch
{
// виконується, якщо виняток виник
}
finally
{
// виконується завжди — і при винятку, і без нього
}
Логіка роботи:
- Виконуються інструкції у блоці
try. - Якщо жодного винятку не виникло — після
tryвиконуєтьсяfinally, блокcatchпропускається. - Якщо виняток виник — виконання
tryзупиняється на місці помилки. CLR шукає відповідний блокcatch. - Якщо
catchзнайдено — він виконується, потімfinally. - Якщо
catchне знайдено —finallyвсе одно виконується, а потім програма аварійно завершується.
Перший приклад: парсинг даних пацієнта
Уявімо, що реєстратура вводить вік пацієнта як рядок із зовнішньої форми. Якщо рядок містить нецифрові символи, метод int.Parse() кине FormatException:
Без конструкції try...catch цей код завершився б аварійно з FormatException. З обробником — програма перехоплює помилку, виводить повідомлення і продовжує роботу далі.
Зверніть увагу: рядок "Програма продовжує роботу." виводиться завжди — виняток перехоплено, а не проігноровано.
Поширення винятку по стеку викликів
Виняток не обов'язково обробляється там, де виник. Якщо в методі немає try...catch, виняток «спливає» вгору — до того методу, який викликав поточний. CLR послідовно перевіряє кожен рівень стека, поки не знайде відповідний catch.
Якби у ParseAge не було catch, виняток піднявся б до RegisterPatient. Якби і там не було — до точки виклику, і далі. Це дозволяє централізувати обробку помилок у потрібному місці ієрархії.
Варіант: тільки try...catch (без finally)
Блок finally не є обов'язковим. Конструкція try...catch без finally — найпоширеніший варіант:
Цикл продовжується навіть після помилки: кожен запис обробляється незалежно. Некоректні дані пропускаються, але програма не зупиняється.
Варіант: тільки try...finally (без catch)
finally можна використовувати без catch — якщо ловити виняток не потрібно, але важливо гарантувати виконання завершального коду (наприклад, закрити файл або звільнити ресурс):
Важливо: конструкція try...finally без catch не перехоплює виняток — вона лише гарантує виконання блоку finally перед тим, як виняток продовжить поширюватися вгору. Якщо у жодного виклику не буде catch, програма все одно аварійно завершиться.
Умовні конструкції замість try...catch
Частину потенційних помилок можна уникнути, перевіривши умову заздалегідь, — і це ефективніший підхід, ніж catch. Механізм винятків в CLR витрачає значно більше ресурсів, ніж звичайна умовна перевірка, тому не варто використовувати try...catch там, де можна перевірити коректність даних до виконання операції.
Наприклад, для парсингу існують TryParse-методи:
Метод int.TryParse(string, out int) повертає true, якщо конвертація успішна, і false — якщо ні. При успіху змінна age містить результат. Жодного винятку не виникає — обидва сценарії опрацьовуються нормальним потоком виконання.
Порівняйте: try...catch з int.Parse — це «обробити аварію після зіткнення». TryParse — це «перевірити умови безпеки перед стартом». Другий підхід і семантично коректніший, і продуктивніший.
Коли використовувати try...catch, а коли умовні перевірки
| Ситуація | Рекомендований підхід |
|---|---|
| Парсинг числа з рядка | TryParse + умова |
Перевірка на null |
if (x != null) або ?. |
| Робота з файлом / мережею | try...catch |
| Звернення до бази даних | try...catch |
| Зовнішній код, що може кинути виняток | try...catch |
| Гарантоване звільнення ресурсу | try...finally або using |
Правило просте: якщо помилку можна передбачити і перевірити — перевіряйте. Якщо помилка залежить від зовнішніх умов (файлова система, мережа, зовнішній сервіс) — обробляйте через try...catch.
Підсумок
try— блок захищеного коду, де може виникнути винятокcatch— обробник винятку; виконується лише якщо виняток виникfinally— гарантовано виконується завжди, незалежно від винятку- Виняток поширюється вгору по стеку до першого відповідного
catch - Для передбачуваних помилок краще використовувати умовні перевірки (
TryParse,null-перевірки) — це ефективніше, ніжcatch