JavaRush /Курсы /C# SELF /Блок finally и оператор throw

Блок finally и оператор throw

C# SELF
13 уровень , 2 лекция
Открыта

1. Знакомство с блоком finally

Очистка и освобождение ресурсов

Представьте: вы занимаетесь экспериментами в химической лаборатории и, когда всё закончили (или даже если взорвали пару колбочек), вам всё равно нужно убрать рабочее место, смыть химикаты и выключить свет. Вот для этого и нужен блок finally — он срабатывает всегда, после try и catch, независимо от того, было ли исключение или нет.


try
{
    // Здесь пишем "опасный" код, который может вызвать исключение
}
catch
{
    // Здесь ловим и обрабатываем исключения
}
finally
{
    // Этот код выполнится всегда, исключение было или не было
}
Структура try-catch-finally в C#

Для чего это нужно? В первую очередь, чтобы гарантированно освободить ресурсы: закрыть файл, соединение с базой данных, разблокировать дверь в серверную... Если бы не было 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 сработал.");
}
finally выполняется даже при выбрасывании исключения

Результат:


До ошибки...
Catch ловит ошибку.
Finally сработал.

И если нет подходящего catch, finally всё равно отработает перед тем, как программа вылетит.

Нюансы: что произойдет, если в finally тоже вызывать throw?

Если в finally произойдет throw, то это исключение заменит предыдущее. То есть информация о том, что произошло в try/catch, будет потеряна. Вот почему не рекомендуется кидаться throw в finally, если внутри уже было исключение.


try
{
    throw new Exception("Ошибка в try");
}
finally
{
    throw new Exception("Ошибка в finally");
}
// Итог: наружу выйдет "Ошибка в finally"
Исключение из finally заменяет предыдущее

5. Практические рекомендации и типичные ошибки

Что чаще забывают новички

  • Не использовать блок finally при освобождении ресурсов, полагаясь только на catch.
  • Помещать код, который может вызывать новые исключения, внутрь finally — это приводит к неожиданным ошибкам.
  • Забывать, что return внутри try не «обходит» finally — он всегда выполнится.

Альтернатива finally: что выбрать?

С появлением конструкции using (которую мы подробно изучим чуть позже), освобождение ресурсов стало ещё удобнее, но по сути, using под капотом использует тот же finally. Для любых нестандартных ситуаций (например, разблокировка или отправка сообщения об ошибке) — всё равно приходится использовать finally.

За что любят finally на собеседованиях

Каждый рекрутер, который хоть раз писал сервер под высокой нагрузкой, любит задавать вопросы про finally. Обычно спрашивают: «Что произойдёт, если в try стоит return, а в finallythrow?» или «Гарантировано ли освобождение ресурсов при исключениях?». И теперь у вас есть не просто ответы, а понимание. Вы сможете не только рассказать, как работает finally, но и объяснить, зачем он нужен, когда его использовать, и почему без него ни один серьёзный код не обходится.

2
Задача
C# SELF, 13 уровень, 2 лекция
Недоступна
Простое использование блока finally
Простое использование блока finally
2
Задача
C# SELF, 13 уровень, 2 лекция
Недоступна
Повторное выбрасывание исключения
Повторное выбрасывание исключения
Комментарии (1)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
Ra Уровень 35 Student
3 октября 2025
Шарп вообще чилит, можно писать вообще просто catch, и поймается всё. И throw c повторным выбрасыванием - в Java этого тоже нет, есть только throw ex.