Skip to content

Instantly share code, notes, and snippets.

@eterekhin
Last active March 16, 2020 17:41
Show Gist options
  • Save eterekhin/da6e51529d134f592ae1974eec70ed00 to your computer and use it in GitHub Desktop.
Save eterekhin/da6e51529d134f592ae1974eec70ed00 to your computer and use it in GitHub Desktop.

В c# базовый класс для Exception'ов - Exception. В C# нельзя выбросить исключение другого типа. При возникновении исключения CLR начинает подниматься по стеку вверх и искать подходящий обработчик(блок catch), при этом вызывая все блоки finally, использует pattern maching для определения первого подходящео блока catch и вызывает его записывая трассировку стека от места возникновения исключения до места обработки, далее вам нужно выполнить код, который будет обрабатывать исключения и (возможно) выбрасывать его снова, в случае повторного выброса важно знать, что при таком выбросе трассировка будет сохранена(то же самое исключение будет возбуждено повторно):

А при таком - нет(на самом деле потеряется еще и TargetSite, будет возбуждено новое исключение):

Для того, чтобы просмотреть все необработанные исключения приложения можно подписаться на событие AppDomain'а - UnhandledException

Трассировка в данном случае проходит от места выброса исключения до обработчика, если же нужна полная трассировка до возникновения исключения:

В IL код записываются слова try и catch, также интересно организован блок finally:

Блок try{}catch{} вложен внутрь блока try{} finally{}, видимо в c# коде было сделано выпрямление чтобы не делать вложенность, но тем самым у оператора finally не та семантика, на самом же деле finally никак не зависит от catch и может использоваться без него. Блок finally выполняется в коде практически всегда(если поток прерывается и при этом не генерируется ThreadAbortException - тогда не выполняется), удивительно, что даже если поток отменен (thread.Abort), то finally все-равно выполнится до конца перед остановкой потока.
Также catch завершает свою работу даже если поток остановлен.

Вот последовательность выполнения блоков в такой ситуации:

Структура Exception

Базовый Exception имеет несколько интересных полей:

  1. string Message - существует ctor принимающий message, заполняется при выбросе исключения
  2. IDictionary Data( на самом деле IDictionary<object,object>) - существует ctor, который принимает его, словарик, который должен помочь коду получившему исключение извлечь больше информации о том, что случилось
  3. Exception InnerException - существует ctor, принимающий InnerException - нужен когда исключение генерируется при обработке другого исключения
  4. string StackTrace - трассировка от места возникновения исключения до обработчика
  5. TargetSite - метод в котором сгенерировано исключение

О пользе конструкции try finally

  1. using, foreach, lock, деструктор - везде используется сочетание try{}finally{}, это удобно потому что блок в finally выполнится практически в любом случае (кроме Envorement.FailFast)
    в using'e и foreach - вызывается dispose у disposable Объекта и итератора соответсвенно
    lock - выполнятся Monitor.Exit(), для освобождения ресурса
    деструктор - вызывается base.Finalize();

Рефлексивный вызов методов

При таком вызове исключение я методах оборачиваются в TargetInvocationException, а сами хранятся в InnerException, такой прием помогает явно на то, где было сгенерировано исключение(при использовании dynamic такой проблемы нет).

Лучше такой подход реализован в статических кторах, если статический ктор типа генерирует исключение, то оно оборачивается в TypeInitializationException, это особенно важно потому что статический ктор вызывается неявно при первом обращении к экземпляру такого типа

Как правильно обрабатывать исключения

Рихтер считает, что вы должны полностью подавлять исключения только если разрабатываете приложение которое взаимодействует с пользователями, поскольку пользователи не должны видеть ошибок. При разработке библиотеки допустимо перехватывать исключение обрабатывать (например, закрывать файл) и снова генерировать такое же, сохраняя трассировку стека. Также он считает, что иногда в блоке catch стоит вернуть новое исключение вместо полученного, сохранив полученное в поле InnerException, для того, что пользователь библиотеки на своем уровне понял в чем именно проблема, без погружения в сложные технические детали(например, если исключение из-за того, что не удалось считать файл, а пользователь вообще не знает о реализации, то нужно сообщить ему только о том, что метод вызвать не удалось. По мнению Рихтера - метод должен быть готов к тому, что будет сгенерировано исключение и должен уметь обработать эту ситуацию, сообщив вызывающему коду, что произошло исключение

В чем различие между обработкой исключений в управляемых и неуправляемых языках

В c++ если при создании объекта произошло исключение, то должен быть вызван деструктор, тем самым компилятор должен генерировать код, обрабатывающий такое поведение. В управляемых языках можно просто понадеяться на сборщик мусора - мы разделили ответственность за создание(программист) и удаление объектов(сборщик мусора) - тем самым сделав этот процесс проще

TryXXX

По мнению Рихтера выброс и перехват исключения самый удобный способ обработки исключений и он предлагает использовать TryXXX методы только если подход с генерацией исключения бьет по производительности, это спорно, поскольку существует вероятность что вызывающий код не обработает исключение и получит exception на своей стороне. Можно заставить клиентов обрабатывать ошибки, например используя тип Result<TError,TSuccess> https://tproger.ru/translations/functional-sharp-4/

CER (Constrained Executing Region)

Если приложение имеет состояние, то важно, чтобы оно не могло испортиться во всех случаях. Особенно подлые случаи, когда exception происходит в блоках catch и finally. Есть такие виды exception'ов, от которых не получится в любом случае, например Out of memory или Stack Over Flow, но можно попытаться заранее обработать код, который может привести к OutOfMemory

(дописать про CER, RuntimeHelpers, ReliabilityContractAttribute)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment