Будет ли попытаться / наконец (без Catch) всплыть исключение?

116

Я почти уверен, что ответ - ДА. Если я использую блок Try finally, но не использую блок Catch, то любые исключения БУДУТ пузыриться. Верный?

Есть какие-нибудь мысли о практике в целом?

Сет

Сет Спирмен
источник

Ответы:

131

Да, безусловно, будет. Предполагая, что ваш finallyблок, конечно, не генерирует исключение, и в этом случае он эффективно «заменит» тот, который был сгенерирован изначально.

Джон Скит
источник
13
@David: Вы не можете вернуться из блока finally в C #.
Джон Скит,
3
Документация msdn также подтверждает этот ответ: в качестве альтернативы вы можете перехватить исключение, которое может быть сгенерировано в блоке try оператора try-finally выше по стеку вызовов. То есть вы можете перехватить исключение в методе, вызывающем метод, содержащий инструкцию try-finally, или в методе, вызывающем этот метод, или в любом методе в стеке вызовов. Если исключение не обнаружено, выполнение блока finally зависит от того, выберет ли операционная система запуск операции очистки исключения.
широкополосный
60

Есть какие-нибудь мысли о практике в целом?

Да. Будьте осторожны . Когда ваш блок finally работает, вполне возможно, что он работает из- за необработанного, неожиданного исключения . Это означает, что что-то сломано и может произойти что- то совершенно неожиданное .

В этой ситуации, возможно, вам вообще не следует запускать код в блоках finally. Код в блоке finally может быть построен так, чтобы предполагать, что подсистемы, от которых он зависит, исправны, хотя на самом деле они могут быть глубоко сломаны. Код в блоке finally может усугубить ситуацию.

Например, я часто вижу такие вещи:

DisableAccessToTheResource();
try
{
    DoSomethingToTheResource();
}
finally
{
    EnableAccessToTheResource();
}

Автор этого кода думает: «Я делаю временную мутацию состояния мира; мне нужно восстановить состояние, которое было до того, как меня вызвали». Но давайте подумаем обо всех случаях, когда это могло пойти не так.

Во-первых, доступ к ресурсу уже мог быть отключен вызывающей стороной; в этом случае этот код повторно включает его, возможно, преждевременно.

Во-вторых, если DoSomethingToTheResource выдает исключение, правильно ли сделать, чтобы разрешить доступ к ресурсу ??? Код, управляющий ресурсом, неожиданно сломан . По сути, этот код говорит: «Если код управления сломан, убедитесь, что другой код может вызвать этот сломанный код как можно скорее, чтобы он тоже мог выйти из строя ужасно ». Это кажется плохой идеей.

В-третьих, если DoSomethingToTheResource генерирует исключение, то как мы узнаем, что EnableAccessToTheResource также не будет генерировать исключение? Какой бы ужас ни случился, использование ресурса может также повлиять на код очистки, и в этом случае исходное исключение будет потеряно, а проблему будет труднее диагностировать.

Я обычно пишу такой код без использования блоков try-finally:

bool wasDisabled = IsAccessDisabled();
if (!wasDisabled)
    DisableAccessToTheResource();
DoSomethingToTheResource();
if (!wasDisabled)
    EnableAccessToTheResource();

Теперь состояние не изменяется, если в этом нет необходимости. Теперь состояние вызывающего абонента не изменится. И теперь, если DoSomethingToTheResource дает сбой, мы не включаем доступ повторно. Мы предполагаем, что что-то глубоко сломано, и не рискуем ухудшить ситуацию, пытаясь продолжить выполнение кода. Пусть звонящий сам решит проблему, если сможет.

Итак, когда лучше запустить блок finally? Во-первых, когда ожидается исключение. Например, вы можете ожидать, что попытка заблокировать файл может потерпеть неудачу, потому что кто-то другой заблокировал его. В этом случае имеет смысл перехватить исключение и сообщить об этом пользователю. В этом случае снижается неуверенность в том, что сломано; вы вряд ли сделаете хуже, прибравшись.

Во-вторых, когда очищаемый ресурс является дефицитным системным ресурсом. Например, имеет смысл закрыть дескриптор файла в блоке finally. («Использование» - это, конечно, всего лишь еще один способ написания блока try-finally.) Содержимое файла может быть повреждено, но сейчас вы ничего не можете с этим поделать. В конце концов дескриптор файла будет закрыт, так что это может быть скорее раньше, чем позже.

Эрик Липперт
источник
7
Еще больше примеров того, как мы, как отрасль, еще не собрались вместе, когда дело доходит до обработки ошибок. Не то чтобы я мог предложить что-то лучшее, чем исключения, но я надеюсь, что в будущем будет что-то, что с большей вероятностью приведет к тому, что правильный курс действий будет принят достаточно легко.
Джон Скит,
В vb.net код в блоке finally может знать, какое исключение ожидает обработки. Если настроить иерархию исключений, чтобы отличать «не выполнял; в противном случае состояние нормально» от «состояние нарушено», можно запустить блок finally, только если ожидающее исключение не является одним из плохих. Мне нравится, когда процедуры удаления / очистки улавливают исключения и генерируют DisposerFailedException (который находится в «плохой» категории и включает исходное исключение как InnerException). Я хотел бы видеть стандартный интерфейс iDisposableEx с Dispose (Ex as Exception), чтобы облегчить это.
supercat
Хотя я понимаю, что бывают случаи, когда может возникнуть исключение, когда объект находится в плохом состоянии, а попытка очистки усугубит ситуацию, я думаю, что такие проблемы следует обрабатывать в коде очистки, возможно, с помощью делегатов. Обертка блокировки, например, может предоставлять делегат функции «CheckIfSafeToUnlock (Ex as Exception)», который может быть установлен в то время, когда заблокированный объект находится в недопустимом состоянии, и очищен в противном случае. Перед снятием блокировки обертка могла проверить делегата; если он не пустой, он может запустить его и снять блокировку, только если он вернет true.
supercat
Использование в оболочке такого делегата позволило бы коду, который управлял состоянием объекта, выбрать соответствующее действие очистки, если оно прерывается. Такие действия могут включать в себя восстановление структуры данных и возврат True, выброс другого «более серьезного» исключения, которое обертывает исходное, или разрешение исходному исключению распространяться при возврате False.
supercat
2
Эрик, спасибо за отличный ответ. Думаю, мне следовало задать два вопроса, потому что и вы, и Джон правы. В любом случае, за вас проголосовали. Спасибо за внимание к вопросу.
Сет Спирман