1. Знайомство з блоком finally
Очищення та звільнення ресурсів
Уявіть: ви експериментуєте у хімічній лабораторії, і коли усе закінчили (або навіть якщо підірвали кілька колбочок), вам усе одно треба прибрати робоче місце, змити хімікати й вимкнути світло. Саме для цього потрібен блок finally — він спрацьовує завжди, після try і catch, незалежно від того, стався виняток чи ні.
try
{
// Тут пишемо "небезпечний" код, який може спричинити виняток
}
catch
{
// Тут перехоплюємо та обробляємо виняток
}
finally
{
// Цей код виконається завжди — був виняток чи ні
}
Навіщо це потрібно? Насамперед, щоб гарантовано звільнити ресурси: закрити файл, з’єднання з базою даних, розблокувати двері до серверної… Якби не було finally, після помилки деякі ресурси могли б лишатися «завислими» — а це вже справжня проблема. Наприклад, файл буде заблоковано, і його не зможе відкрити навіть адміністратор.
Чому не можна все писати у catch?
Чи могли б ми просто звільнити ресурс у catch? Теоретично так. Але якщо все минуло добре, catch не виконається. Якщо ми хочемо бути певні, що звільнення ресурсу відбудеться завжди, нам потрібен finally.
У реальному житті часто трапляються такі завдання, де неважливо, був успіх чи провал — прибирання обов’язкове. Саме для цього й придумали finally.
2. Особливості блоку finally
Коли finally не спрацьовує?
Питання з підступом! finally виконується завжди. Навіть якщо в блоці try викликається return (ранній вихід з методу) або генерується новий виняток, finally усе одно буде виконано.
static void Test()
{
try
{
Console.WriteLine("До return");
return;
}
finally
{
Console.WriteLine("finally усе одно спрацює!");
}
}
//Виклик Test()
Test();
Виведе:
// До return
// finally усе одно спрацює!
Але якщо раптом із вашим застосунком станеться «жорстке падіння» (наприклад, вимкнули комп’ютер, процес примусово завершили або припинилася робота CLR), блок finally не виконається. Тут уже нічого не вдієш.
Оператор finally і стек викликів
Стек викликів ми детальніше розглянемо на наступній лекції, а зараз коротко: це мовби стос викликаних методів, яким програма «спускається», якщо не знаходить відповідного catch.
Ще один важливий момент: якщо в try виник виняток, а в catch його не перехоплено (наприклад, немає відповідного обробника), програма залишає поточний метод і рухається далі стеком викликів, доки не знайде відповідний catch. Але перед цим обов’язково виконається блок finally на кожному рівні стека.
3. Оператор throw: як самостійно кидати винятки
Що таке throw і навіщо він потрібен
Іноді простого перехоплення винятку недостатньо — буває, треба створити власну помилку і «кинути» її назовні. Саме для цього існує оператор throw.
throw new Exception("Це моя спеціальна помилка!");
throw буквально каже CLR: «Я тут виявив щось дуже погане, кидаю свій Exception, далі нехай розбирається той, хто викликав цей код».
throw без створення нового винятку
Можна використовувати throw; усередині блоку catch, щоб повторно кинути щойно перехоплений виняток — наприклад, якщо ви обробили частину помилки, але далі відповідальність за обробку хочете делегувати вищому рівню коду.
try
{
DangerousOperation();
}
catch (Exception ex)
{
LogError(ex);
throw; // повторно кидає поточний виняток, стек викликів буде збережено
}
Якби ми написали throw ex;, інформацію про стек викликів було б втрачено — це погана практика.
4. Як finally працює разом із throw?
finally спрацьовує навіть під час кидання винятку
Давайте перевіримо, що станеться, якщо всередині try відбувається throw, але в нас є й finally:
try
{
Console.WriteLine("До помилки...");
throw new Exception("Помилка під час try!");
}
catch
{
Console.WriteLine("Catch перехоплює помилку.");
}
finally
{
Console.WriteLine("Finally спрацював.");
}
Результат:
До помилки...
Catch перехоплює помилку.
Finally спрацював.
І якщо немає відповідного catch, finally усе одно відпрацює перед тим, як програма аварійно завершиться.
Нюанси: що буде, якщо у finally теж викликати throw?
Якщо у finally станеться throw, цей виняток замінить попередній. Тобто інформацію про те, що сталося в try/catch, буде втрачено. Тому не рекомендують кидати throw у finally, якщо всередині вже стався виняток.
try
{
throw new Exception("Помилка у try");
}
finally
{
throw new Exception("Помилка у finally");
}
// Підсумок: назовні вийде "Помилка у finally"
5. Практичні поради та типові помилки
Що частіше забувають новачки
- Не використовувати блок finally для звільнення ресурсів, покладаючись лише на catch.
- Розміщувати код, який може спричинити нові винятки, всередині finally — це призводить до неочікуваних помилок.
- Забувати, що return усередині try не «обходить» finally — він завжди виконається.
Альтернатива finally: що обрати?
З появою конструкції using (ми детально розглянемо її згодом) звільнення ресурсів стало ще зручнішим, але по суті using під капотом використовує той самий finally. Для будь-яких нестандартних ситуацій (наприклад, розблокування чи надсилання повідомлення про помилку) усе одно доводиться використовувати finally.
За що люблять finally на співбесідах
Кожен рекрутер, який хоч раз писав сервер із великим навантаженням, любить ставити запитання про finally. Зазвичай запитують: «Що буде, якщо в try стоїть return, а в finally — throw?» або «Чи гарантоване звільнення ресурсів під час винятків?». І тепер у вас є не просто відповіді, а розуміння. Ви зможете не лише розповісти, як працює finally, а й пояснити, навіщо він потрібен, коли його використовувати і чому без нього жоден серйозний код не обходиться.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ