Блок finally
Уходя из кухни, выключите плиту — что бы ни случилось. Неважно, доготовили вы обед, вас позвали к телефону или вы выбежали по пожарной тревоге: плиту выключают на любом пути из кухни.
В программе это правило можно нарушить и не заметить. Посмотрите, что происходит с плитой, когда в try случается исключение:
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:
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
}Давайте разберём программу по шагам:
Stove onпечатается.int.Parse("x")выбрасываетFormatException, и остатокtryпропускается.catchпечатаетNot a number.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.
1Console.WriteLine(Read("5"));2try3{4 Console.WriteLine(Read("x"));5}6catch (FormatException)7{8 Console.WriteLine("Caught");9}10 11int Read(string text)12{13 try14 {15 return int.Parse(text);16 }17 finally18 {19 Console.WriteLine("Stove off");20 }21}
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, ведь он выполняется всегда:
Console.WriteLine(Read("5"));
int Read(string text)
{
try
{
return int.Parse(text);
}
finally
{
return 0; // ✗
}
}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.