Перейти к содержимому
Теория · Исключения · урок 4 из 9

Блок finally

Уходя из кухни, выключите плиту — что бы ни случилось. Неважно, доготовили вы обед, вас позвали к телефону или вы выбежали по пожарной тревоге: плиту выключают на любом пути из кухни.

В программе это правило можно нарушить и не заметить. Посмотрите, что происходит с плитой, когда в try случается исключение:

C#
Console.WriteLine("Stove on"); // Stove on
try
{
    int minutes = int.Parse("x");
    Console.WriteLine("Cooking for " + minutes);
    Console.WriteLine("Stove off");
}
catch (FormatException)
{
    Console.WriteLine("Not a number"); // Not a number
}

int.Parse("x") выбрасывает исключение, и выполнение сразу переходит в catch. Две строки ниже пропускаются, и Stove off не печатается: плита осталась включённой.

Можно перенести Stove off за блоки try и catch. Здесь это помогло бы, но не всегда. Метод может выйти раньше через return, а исключение другого класса пройдёт мимо catch. Тогда строка после блоков снова не выполнится.

Третий блок: finally

Для такого правила в C# есть третий блок. Блок finally ставят после catch или сразу после try, и он выполняется всегда, когда выполнение покидает try:

C#
Console.WriteLine("Stove on"); // Stove on
try
{
    int minutes = int.Parse("x");
    Console.WriteLine("Cooking for " + minutes);
}
catch (FormatException)
{
    Console.WriteLine("Not a number"); // Not a number
}
finally
{
    Console.WriteLine("Stove off"); // Stove off
}

Давайте разберём программу по шагам:

  1. Stove on печатается.
  2. int.Parse("x") выбрасывает FormatException, и остаток try пропускается.
  3. catch печатает Not a number.
  4. finally печатает Stove off.

Если заменить "x" на "5", исключения не будет. Программа напечатает Stove on, Cooking for 5 и Stove off: finally выполнится и в этот раз.

После return и исключения

Из try можно выйти и раньше: через return или через исключение, которое метод не перехватывает сам. finally выполняется и тогда, перед самым выходом из метода.

Об этом часто спрашивают на собеседовании: «Выполнится ли finally, если в try стоит return?» Ответ — да. finally пропускается, только если программа завершается сразу, как при StackOverflowException из темы о методах.

В программе ниже метод Read выходит из try дважды: первый раз через return, второй — через исключение. Нажимайте «Следующий шаг» и следите, когда печатается Stove off.

ВживуюКогда выполняется finallyшаг 1 из 8 · рамок: 1

Main вызывает Read("5") и ждёт число, чтобы его напечатать.

Стопка рамок показывает, какой вызов сейчас работает

Программа печатает Stove off дважды, и оба раза до того, как выполнение вернётся в Main. Разберём оба выхода.

Выход через return

return int.Parse(text); сначала вычисляет число 5 и запоминает его. Затем выполняется finally, и только после этого метод возвращает 5. Поэтому Stove off печатается раньше, чем Main печатает 5.

Выход через исключение

При "x" исключение выбрасывается внутри try метода Read. Своего catch у Read нет, поэтому исключение идёт к Main, но по дороге выполняется finally. Лишь потом catch в Main печатает Caught.

Здесь аналогия с плитой неточна. Выключив плиту, вы ещё можете передумать и остаться на кухне. А finally выход не отменяет и исключение не перехватывает: после него Read всё равно вернёт 5 или передаст исключение дальше.

Частая ошибка

Хотели, чтобы при ошибке метод возвращал 0, и написали return прямо в finally, ведь он выполняется всегда:

C#
Console.WriteLine(Read("5"));

int Read(string text)
{
    try
    {
        return int.Parse(text);
    }
    finally
    {
        return 0; // ✗
    }
}
TEXT
error CS0157: Control cannot leave the body of a finally clause

Программа не компилируется: выйти из тела finally через return нельзя. Блок finally только наводит порядок, а вернуть значение по умолчанию можно из catch: catch (FormatException) { return 0; }.

Главное

  • finally выполняется при любом выходе из try: после обычного конца, после catch, после return и после исключения, которое идёт дальше.
  • При return значение вычисляется до finally, а метод возвращает его после.
  • В finally кладут то, что нужно сделать в любом случае: выключить плиту, закрыть файл.
  • return в самом finally запрещён: ошибка CS0157.