Обработка исключений
try/catch/finally, порядок нескольких catch, throw и throw ex, свои исключения и using.
6 вопросов
JuniorТеорияОчень частоКакие роли играют try, catch и finally в обработке исключений?
Какие роли играют try, catch и finally в обработке исключений?
try оборачивает код, который может дать сбой. Когда выбрасывается исключение, среда выполнения ищет среди блоков catch подходящий по объявленному типу и выполняет его. finally выполняется после в любом случае, было исключение или нет, поэтому туда помещают очистку ресурсов. У try должен быть хотя бы один catch или finally.
Типичные ошибки
- ✗Считать, что
finallyвыполняется только при исключении, а не всегда - ✗Писать общий
catch (Exception), скрывающий ошибки проглатыванием любого сбоя - ✗Забывать, что
tryбез хотя бы одногоcatchилиfinallyнедопустим
Уточняющие вопросы
- →Что произойдёт, если исключение выброшено, но ни один catch не подходит по типу?
- →Может ли сам finally выбросить исключение, и что станет с исходным?
JuniorТеорияЧастоЧто делает оператор using, и с какими типами его можно применять?
Что делает оператор using, и с какими типами его можно применять?
using детерминированно вызывает Dispose() у ресурса при выходе из области, даже при исключении, компилируясь в скрытый try/finally. Тип должен реализовывать IDisposable. Применяется для неуправляемых или дефицитных ресурсов: файлов, сокетов, соединений с БД. Форма-объявление (using var f = ...;) освобождает ресурс в конце охватывающего блока.
Типичные ошибки
- ✗Путать оператор
usingс директивойusingдля пространств имён - ✗Считать, что он работает с любым типом, а не только реализующим
IDisposable - ✗Полагать, что
Dispose()пропускается при выходе из блока через исключение
Уточняющие вопросы
- →Чем using-объявление отличается от классического оператора using по области?
- →В чём разница между Dispose и финализатором при освобождении ресурсов?
MiddleТеорияЧастоКак определить своё исключение, и когда его создание оправдано?
Как определить своё исключение, и когда его создание оправдано?
Наследуйтесь напрямую от Exception (не от устаревшего ApplicationException), предоставьте стандартные конструкторы — без параметров, с сообщением и с сообщением плюс вложенным исключением — и добавьте свойства с доменным контекстом. Создавайте своё исключение лишь когда вызывающим нужно ловить и обрабатывать этот конкретный сбой иначе; иначе переиспользуйте встроенный тип, как ArgumentException или InvalidOperationException.
Типичные ошибки
- ✗Наследоваться от устаревшего
ApplicationExceptionвместоException - ✗Опускать конструкторы с сообщением и вложенным исключением, теряя контекст обёртки
- ✗Изобретать свой тип, когда подходит встроенный, как
ArgumentException
Уточняющие вопросы
- →Почему своё исключение должно сохранять исходную ошибку как вложенное исключение?
- →Когда бросок встроенного типа лучше определения собственного?
MiddleТеорияЧастоКогда выполняется блок finally, и есть ли случай, когда он пропускается?
Когда выполняется блок finally, и есть ли случай, когда он пропускается?
finally выполняется независимо от того, было выброшено исключение или нет, и даже когда try или catch делает return — среда выполняет finally до фактического выхода управления. Поэтому это надёжное место для очистки. Он пропускается лишь при жёстком завершении процесса: Environment.FailFast, неперехватываемый StackOverflowException или убийство процесса ОС.
Типичные ошибки
- ✗Считать, что
finallyпропускается, когда исключение выходит из try наружу - ✗Думать, что
returnвнутри try или catch тихо обходит блок finally - ✗Полагать, что
finallyвыполняется всегда, даже при FailFast или переполнении стека
Уточняющие вопросы
- →Если и try, и finally делают return, какое возвращаемое значение победит?
- →Почему долгий finally может задержать или заблокировать распространение исключения?
MiddleТеорияИногдаКак среда выбирает один из нескольких catch при одном try, который выполнится?
Как среда выбирает один из нескольких catch при одном try, который выполнится?
Блоки catch проверяются сверху вниз, и побеждает первый, чей тип совпадает с выброшенным исключением — выполняется только этот один обработчик, затем управление идёт в finally. Поэтому более конкретные типы должны идти перед более общими. Если широкий catch, как Exception, стоит перед более узким, узкий блок недостижим и компилятор сообщает об ошибке.
Типичные ошибки
- ✗Ставить
catch (Exception)первым, делая конкретные блоки ниже недостижимыми - ✗Ожидать, что несколько подходящих catch выполнятся для одного исключения
- ✗Думать, что среда выбирает ближайший тип, а не первый по порядку
Уточняющие вопросы
- →Как фильтр исключения (
catch when (...)) меняет, какой блок выбирается? - →Что происходит, когда ни один catch не подходит по типу выброшенного исключения?
SeniorТеорияИногдаЧем внутри catch различаются throw; и throw ex;, и что предпочесть?
Чем внутри catch различаются throw; и throw ex;, и что предпочесть?
Внутри catch throw; повторно бросает то же исключение и сохраняет его исходный стек вызовов, поэтому кадры, указывающие на настоящий источник, остаются. throw ex; бросает пойманный объект как новый, сбрасывая стек вызовов на строку повторного броска и стирая, где сбой действительно начался. Предпочитайте throw;; throw ex; не нужен практически никогда, ведь он скрывает источник.
Типичные ошибки
- ✗Использовать
throw ex;для повтора, сбрасывая и теряя исходный стек вызовов - ✗Считать, что
throw;иthrow ex;ведут себя во время выполнения одинаково - ✗Думать, что
throw;проглатывает исключение, а не бросает его вверх
Уточняющие вопросы
- →Как обёртка в новое исключение с вложенным сравнивается с голым повторным броском?
- →Что даёт ExceptionDispatchInfo.Capture сверх обычного throw;?