Что такое блок finally в C#?

Ответ

Блок finally — это часть конструкции try-catch-finally в C#, предназначенная для гарантированного выполнения кода очистки (cleanup), независимо от того, как было завершено выполнение блока try (или catch).

Когда выполняется finally? Код в finally выполняется всегда после выхода из блока try (или catch), даже если:

  1. Исключение было выброшено и перехвачено (catch).
  2. Исключение было выброшено и НЕ перехвачено (после выполнения finally исключение продолжит всплывать).
  3. В блоке try или catch был выполнен return.
  4. В блоке try или catch был выполнен break или continue, ведущий выходу из блока.

Типичный use-case — освобождение неуправляемых ресурсов:

FileStream fileStream = null;
try
{
    fileStream = File.OpenRead("data.txt");
    // Чтение данных, которое может вызвать IOException
    var data = new byte[fileStream.Length];
    fileStream.Read(data, 0, (int)fileStream.Length);
}
catch (IOException ex)
{
    Console.WriteLine($"Ошибка чтения файла: {ex.Message}");
}
finally
{
    // Этот код выполнится в любом случае: и при успехе, и при исключении.
    // Критически важно закрыть файловый дескриптор.
    fileStream?.Close();
    // В современных C# для этого используют 'using', который компилируется в try-finally.
}

Современная альтернатива — оператор using: Для объектов, реализующих IDisposable, блок finally с вызовом Dispose() можно заменить на:

using (var fileStream = File.OpenRead("data.txt"))
{
    // Работа с fileStream
} // Dispose() будет вызван автоматически здесь, даже при исключении

Когда finally НЕ выполнится? В крайних случаях: при фатальной ошибке среды (Environment.FailFast), переполнении стека (StackOverflowException) или аварийном завершении процесса (например, kill -9).

Ответ 18+ 🔞

А, вот эта штука, finally! Ну, слушай, это как тот друг, который всегда приходит на помощь, даже если ты его нахуй послал. Представь себе.

Вот у тебя есть try — это как попытка завести старенькую «копейку» зимой. Может завестись, а может и нет, блядь. А catch — это когда ты уже стоишь с тросом и ругаешься, потому что не завелась. Так вот, finally — это когда ты, в любом случае, даже если машина завелась и уехала, или если она сдохла и ты её пнул, — ты всё равно идёшь в гараж и кладёшь трос на место. Понимаешь? Гарантированно. Хуй там плавал — ты это сделаешь.

Когда он врубается, этот твой finally? Да всегда, ёпта!

  1. Если в try всё прошло гладко, как по маслу — finally выполнится.
  2. Если в try случилась дичь, и её поймал catchfinally выполнится.
  3. Если дичь случилась, а catch её не поймал — всё равно, перед тем как всё нахуй сломается и исключение полетит выше, finally выполнится.
  4. Даже если ты в try или catch написал return и собрался сваливать — нет, блядь, сначала выполнится finally, а уж потом функция отдаст результат. Вот такой он наглый.

Зачем это, спросишь? Да чтобы уборку сделать, сука! Самый классический случай — работа с каким-нибудь ресурсом, который нельзя просто так бросить. Типа файла, сетевого соединения или дескриптора из какой-нибудь старой библиотеки.

Смотри, вот как это выглядело раньше, до того как все поумнели:

FileStream fileStream = null; // Создаём переменную, пока пустую, как твои обещания
try
{
    fileStream = File.OpenRead("data.txt"); // Пытаемся открыть файл
    // Допустим, тут мы что-то читаем, а файл-то битый! Исключение летит!
    var data = new byte[fileStream.Length];
    fileStream.Read(data, 0, (int)fileStream.Length); // На этой строчке может быть пиздец
}
catch (IOException ex) // Ловим эту самую ошибку
{
    Console.WriteLine($"Ошибка чтения файла: {ex.Message}"); // Ругаемся в консоль
}
finally
{
    // А ВОТ ЭТО — святое. Мы здесь в любом случае, даже если всё сломалось или, наоборот, получилось.
    // Файловый дескриптор нужно закрыть, а то система обидится.
    if (fileStream != null)
    {
        fileStream.Close(); // Закрываем. Точно. Гарантированно.
    }
}

Но народ ленивый, и придумали using. Это как finally, но для ленивых и умных. Если твой объект умеет IDisposable (то есть его нужно «прибрать»), то можно сделать так:

using (var fileStream = File.OpenRead("data.txt")) // Создали
{
    // Поработали с ним тут
} // А тут, за кулисами, компилятор сам развернёт это в try-finally и вызовет fileStream.Dispose() (тот же Close()). Автоматом! Даже если внутри был пиздец.

А когда finally не сработает? Да когда уже совсем пиздец, Карл! Если процесс убили через kill -9, если система сама легла, или если случилось что-то совсем уж экзистенциальное, вроде StackOverflowException или вызова Environment.FailFast. Во всех остальных, более-менее земных ситуациях — finally твой верный страховочный трос.