Управление исключениями не работает

Управление исключениями с помощью отладчика в Visual Studio

Исключение указывает на состояние ошибки, возникающее при выполнении программы. Можно указать отладчику, какие исключения или наборы исключений должны вызывать прерывание и в какой момент нужно прервать выполнение (то есть приостановить отладчик). Когда отладчик прерывает работу, он показывает, где было создано исключение. Кроме того, можно добавлять или удалять исключения. После открытия решения в Visual Studio в разделе Отладка > Windows > Параметры исключений откройте окно Параметры исключений.

Предоставьте обработчики, реагирующие на наиболее важные исключения. Сведения о том, как добавлять обработчики для исключений, см. в разделе Исправление ошибок путем написания более качественного кода C#. Кроме того, узнайте, как настроить отладчик, чтобы всегда прерывать выполнение для некоторых исключений.

При возникновении исключения отладчик записывает его сообщение в окно Вывод. Он может прервать выполнение в следующих случаях.

  • Создается исключение, которое не обрабатывается.
  • Отладчик настроен на прерывание выполнения до вызова обработчика.
  • Задан параметр Только мой код, и отладчик настроен на прерывание по любому исключению, не обрабатываемому в коде пользователя.

В ASP.NET существует обработчик исключений верхнего уровня, отображающий станицы ошибок в браузере. Он не прерывает выполнение до тех пор, пока не будет включен параметр Только мой код. Пример см. в разделе Настройка отладчика для продолжения в случае не обработанных пользователем исключений ниже.

В приложениях, написанных на Visual Basic, отладчик управляет всеми ошибками как исключениями, даже при использовании обработчиков ошибок типа On Error.

Настройка отладчика для прерывания выполнения при создании исключения

Отладчик может прервать выполнение приложения в точке возникновения исключения, чтобы вы могли проверить исключение еще до вызова обработчика.

В окне Параметры исключений (Отладка > Windows > Параметры исключений) разверните узел для категории исключений, например Исключения среды CLR. Затем установите флажок для конкретного исключения в этой категории, например System.AccessViolationException. Можно также выбрать всю категорию исключений.

Для поиска конкретных исключений можно воспользоваться окном Поиск на панели инструментов Параметры исключений или применить функцию поиска для фильтрации определенных пространств имен (например, System.IO).

Если вы выберете исключение в окне Параметры исключений, выполнение отладчика будет прерываться везде, где возникает исключение, независимо от того, обработано ли оно. Теперь исключение называется первым экземпляром исключения. Ниже приведено несколько примеров.

В следующем консольном приложении C# метод Main создает исключение AccessViolationException внутри блока try/catch .

Если исключение AccessViolationException отмечено в окне Параметры исключений, при выполнении этого кода в режиме отладчика произойдет останов на строке throw . После этого выполнение можно продолжить. В консоли должны отображаться обе строки.

Но в ней не отображается строка here .

Консольное приложение C# ссылается на библиотеку классов с классом, имеющим два метода. Один метод создает исключение и обрабатывает его, в то время как второй метод создает такое же исключение, но не обрабатывает его.

Далее приводится метод Main() консольного приложения:

Если исключение AccessViolationException отмечено в окне Параметры исключений, при выполнении этого кода в режиме отладчика произойдет останов на строке throw в методах ThrowHandledException() и ThrowUnhandledException() .

Чтобы восстановить параметры исключений до значений по умолчанию, выберите Восстановить для списка параметры по умолчанию:

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

При отладке кода .NET или JavaScript с параметром Только мой код можно указать отладчику не прерывать выполнение при возникновении исключений, которые не обрабатываются в пользовательском коде, но обрабатываются в другом месте.

В окне Параметры исключений откройте контекстное меню, щелкнув правой кнопкой мыши метку столбца, а затем выберите Показать столбцы > Дополнительные действия. (Если параметр Только мой код отключен, данная команда не отображается.) Отобразится третий столбец с именем Дополнительные действия.

Для исключения, у которого отображается Продолжить, если не обрабатывается в пользовательском коде в этом столбце, отладчик продолжает работу, если это исключение не обрабатывается в пользовательском коде, но обрабатывается в другом месте.

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

Например, веб-приложения ASP.NET обрабатывают исключения путем их преобразования в код состояния HTTP 500 (Обработка исключений в веб-API ASP.NET), что может затруднить определение источника исключения. В следующем примере пользовательский код вызывает метод String.Format() , который создает FormatException. Выполнение прерывается следующим образом.

Добавление и удаление исключений

Исключения можно добавлять и удалять. Чтобы удалить тип исключения из категории, выберите исключение и нажмите кнопку Удалить выбранное исключение из списка (знак «минус») на панели инструментов Параметры исключений. Или щелкните исключение правой кнопкой мыши и выберите Удалить в контекстном меню. Удаление исключения аналогично снятию флажка для исключения и заключается в том, что при возникновении исключения отладчик продолжит выполнение.

В окне Параметры исключений выберите одну из категории исключений (например, Среда CLR).

Нажмите кнопку Добавить исключение в выбранную категорию (знак «плюс»).

Введите имя исключения (например, System.UriTemplateMatchException).

Исключение будет добавлено в список (в алфавитном порядке) и будет автоматически выбрано.

Чтобы добавить исключение в категории «Исключения доступа к памяти GPU», «Исключения среды выполнения JavaScript» или «Исключения Win32», необходимо включить код ошибки, а также описание.

Читайте также:  Как настроит музыкальный центр technics

Проверьте правильность написания! В окне Параметры исключений не проверяется существование добавленного исключения. Поэтому при вводе Sytem.UriTemplateMatchException появится запись для этого исключения (а не для System.UriTemplateMatchException).

Параметры исключения сохраняются в файл SUO решения и таким образом применяются к конкретному решению. Параметры конкретного исключения нельзя повторно использовать в решениях. Сейчас сохраняются только добавленные исключения. Удаленные исключения не сохраняются. Вы можете добавить исключение, закрыть и повторно открыть решение — исключение будет находиться в нем по-прежнему. Однако при удалении исключения, закрытии и повторном открытии решения исключение появится снова.

В окне Параметры исключений поддерживаются универсальные типы исключений на C#, но не на Visual Basic. Чтобы делать останов при возникновении таких исключений, как MyNamespace.GenericException , необходимо добавить исключение в виде MyNamespace.GenericException’1. То есть, если создано следующее исключение в коде:

Вы можете добавить исключение в окне Параметры исключений, используя предыдущую процедуру:

Добавление условий в исключение

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

Добавление условий в исключение поддерживается, начиная с Visual Studio 2017.

Чтобы добавить условные исключения, выполните следующие действия.

Нажмите кнопку Изменить условия в окне «Параметры исключений» или щелкните правой кнопкой мыши исключение и выберите Изменить условия.

Чтобы добавить дополнительное условие к исключению, выберите Добавить условие. Отобразятся строки дополнительные условий.

Для каждой строки условия введите имя модуля и измените список операторов сравнения на Равно или Не равно. Можно указать подстановочные знаки ( \* ) в имени, чтобы выбрать более одного модуля.

Если необходимо удалить условие, выберите X в конце строки условия.

Источник

Исключения и обработка исключений

Функции обработки исключений в языке C# помогают вам справиться с непредвиденными или исключительными проблемами, которые возникают при выполнении программы. При обработке исключений используются ключевые слова try , catch и finally для действий, которые могут оказаться неудачными. Это позволяет обрабатывать ошибки так, как кажется разумным, а также правильно высвобождать ресурсы. Исключения могут создаваться средой выполнения (CLR), платформой .NET , библиотеками сторонних поставщиков или кодом самого приложения. Чтобы создать исключение, используйте ключевое слово throw .

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

В этом примере метод выполняет проверку деления на нуль и перехватывает ошибку. Если не использовать обработку исключений, такая программа завершит работу с ошибкой DivideByZeroException was unhandled (Исключение DivideByZero не обработано).

Общие сведения об исключениях

Исключения имеют следующие свойства.

  • Исключения представляют собой типы, производные в конечном счете от System.Exception .
  • Используйте блок try для выполнения таких инструкций, которые могут создавать исключения.
  • Когда внутри такого блока try возникает исключение, поток управления переходит к первому подходящему обработчику исключений в стеке вызовов. В C# ключевое слово catch обозначает обработчик исключений.
  • Если для созданного исключения не существует обработчиков, выполнение программы прекращается с сообщением об ошибке.
  • Не перехватывайте исключение, если вы не намерены его обрабатывать с сохранением известного состояния приложения. Если вы перехватываете System.Exception , создайте его заново в конце блока catch , используя ключевое слово throw .
  • Если блок catch определяет переменную исключения, ее можно использовать для получения дополнительных сведений о типе созданного исключения.
  • Программа может явным образом создавать исключения с помощью ключевого слова throw .
  • Объекты исключения содержат подробные сведения об ошибке, например состояние стека вызовов и текстовое описание ошибки.
  • Код в блоке finally выполняется, даже если создано исключение. Используйте блок finally , чтобы высвободить ресурсы, например закрыть потоки и файлы, которые были открыты внутри блока try .
  • Управляемые исключения реализованы в платформе .NET на основе структурированного механизма обработки исключений Win32. Дополнительные сведения см. в статьях Structured Exception Handling (C/C++) (Структурированная обработка исключений в C и C++) и A Crash Course on the Depths of Win32 Structured Exception Handling (Интенсивное погружение в структурированную обработку исключений на платформе Win32).

Спецификация языка C#

Дополнительные сведения см. в разделе Исключения в Спецификации языка C#. Спецификация языка является предписывающим источником информации о синтаксисе и использовании языка C#.

Источник

Я принес вам решение проблемы с исключениями в C#. Но вам не понравится

Я каждый день пишу код на сишарпе, и натыкаюсь на одну проблему: я трачу кучу времени на то, чтобы решить, как быть, если что-то идёт не по плану.

У меня есть приличный опыт работы с другими языками программирования, и стандартный подход по работе с ошибками в C# мне не нравится. Но языки и платформы устроены так, что ты решаешь проблемы не как считаешь нужным, а так, как принято.

Эти размышления меня измучили, и я систематизировал свои знания и идеи по обработке исключительных случаев.

Возьму простой пример. Допустим у нас есть сервис, который отдаёт нам модель юзера по Id.

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

Давайте посмотрим, какие у нас есть варианты.

Классический подход к таким вещам в C# — исключения

Тут все просто. Метод ищет пользователя, если, не находит — выплевывает исключение.

Читайте также:  Покури со мной не сбивай настрой

Пользоваться таким методом можно вот так:

Плюсы подхода очевидны.

  • Именно этого и ожидает пользователь твоего кода, когда человек использует метод, который в теории может сломаться, первое что он делает — смотрит в его доке, какие исключения выбрасывает этот метод. И затем обрабатывает их.
  • Исключения «всплывают» вверх по стеку вызовов, позволяя легко передать ответственность по обработке ошибки на верхний уровень
  • Поддержка исключений в C# нативная, есть специальные конструкции, чтобы их обрабатывать, удобный механизм обработки специальных типов исключений

Но минусов — тьма. И просто закрыть глаза на них — не получится.

  • Никаких гарантий. Вообще никаких. Человек который использует твой сервис, может даже и не подумать о том, что тут что-то надо обрабатывать. Или обработает не все возможные. Если их не обработают на месте, а обработают выше, в том коде, который на такие исключения не рассчитан — могут возникнуть достаточно большие проблемы.
  • Тебе придется скрупулёзно писать и обновлять документацию такого метода, и компилятор тебе не будет гарантировать, что ты описал всё, что выплевываешь.
  • Очень плохо подходит для случаев, когда вызывающей стороне не нужно знать, почему произошла проблема.
  • В доменном коде тебе постоянно нужно будет добавлять свои типы исключений, они раздувают кодовую базу, часто дублируются и требуют поддержки.
  • Когда метод потенциально выплевывает пять шесть типов исключений, код превращается в нечитаемое говно — и код метода, и код использования.
  • В сишарпе принято использовать интерфейсы. Если есть какой-то сервис, и есть код, который его использует, то мы в этом коде работаем с интерфейсом сервиса. Так вот выкидывается исключение или нет — интерфейс это не определяет НИКАК. Т.е. если кто-то написал класс, который имплементит такой-то интерфейс, и внутри этого класса он выкидывает исключение, и он даже внес это в доку класса — я при использовании этого кода об этом НЕ УЗНАЮ.

Лучше всего понять, чем плохи исключения, можно, когда используешь чужой, плохо задокументированный код. Есть условный метод GetById, а что он станет делать, если не найдет — ну ты понятия не имеешь. Вернет null? Выбросит какое-то исключение? Я писал код, в котором вызывал чужой метод, оборачивал в try-catch, и чекал результат на нулл. Создателя того метода я бы с радостью прибил. Ну, по крайней мере в тот момент.

Ещё один распространненый способ разруливать это — try pattern.

Идея завязана на out параметры в сишарпе. Выглядит вот так:

Если все норм, мы возвращаем true, и присваиваем out переменной user найденное значение. Если не норм, отдаём false, а out переменную заполняем дефолтным значением (в случае с классом это будет null).

Использовать такой метод следует так:

У подхода много плюсов:

  • Он идиоматичен. Такая конвенция знакома всем шарпистам, она используется в родных коллекциях, все знаю, как с этим работать.
  • Способ надежен. Просто проигнорировать возможную проблему не получится. Ведь метод возвращает bool, и пользователь кода вынужден будет обратить внимание на твою задумку.
  • Код использования выглядит достаточно лаконично и понятно.
  • Отлично подходит для ситуаций, когда причина возможной неудачи очевидна — Мы не тащим очевидную информацию в стиле «not found».
  • Не нужно создавать дополнительные файлы исключений, сама реализация очень проста синтаксически — метод не перегружается лишним кодом и докой.
  • Мы явным образом снимаем с себя ответственность за обработку неудачи, и передаем её вызывающему коду.

Минусы тоже есть:

  • Это все ещё конвенция. Никто не мешает пользователю кода просто вызвать метод, не проверять возвращаемое значение, и начать использовать out-параметр.
  • Не подходит для случаев, когда хотим передать вызывающему коду причину ошибки. На самом деле, мы можем добавить второй out-параметр, error: Exception, но тогда мы потеряем бОльшую часть плюсов. Особенно если типов ошибок может быть несколько.
  • Вся лаконичность пойдет к черту, код испортится. Кроме того, это уже не будет общепринятой конвенцией, и есть шанс, что коллеги начнут крутить пальцем у виска. Так же есть вариант определять Enum со статусом операции, и возвращать его вместо bool.
  • Не работает с async-ами. C# это не поддерживает, и, похоже, не будет.
  • Плохо сочетается с новой фичей из C#8 — nullable. Потому что тип out параметра у нас по факту nullable, но если мы скажем об этом компилятору, он заставит пользователя кода проверять его дважды. А если не укажем, то у нас будет nullable параметр, у которого тип — NotNullable. Может вводить в заблуждение, и в целом очень неприятный расклад.

Очень похожий способ — SomeOrDefault.

Тоже распространенный для дотнета подход, когда мы отдаем найденное значение, а иначе null.

А использовать вот так:

  • Он так же идиоматичен. Все знают, что значит постфикс OrDefault, подход используется в родных коллекциях.
  • Начиная с C#8 этот способ так же может быть надежным. Если в вашем проекте включена фича компилятора nullable, и вы не кладете болт на варнинги, SomeOrDefault-а достаточно, чтобы гарантировать обработку провала.
  • Код использования — самое идиоматичное, что вообще можно увидеть в сишарпе. Все всё поймут. И его очень мало.
  • Так же подходит для ситуаций, когда причина возможной неудачи очевидна.
  • Никаких дополнительных файлов и нагрузки на реализатора — это очень легко написать и поддерживать.

Минусы:

  • Это конвенция. Люди могут ей не следовать, или даже не знать о ней.
  • Не подходит для случаев, когда хотим передать вызывающему коду причину ошибки.
  • Если у вызывающего кода не C#8+ с включенным нулабл, единственная гарантия — это нейминг. Очень склизская гарантия.

Наивысшая надежность — Maybe монада.

Идея простая. Есть отдельная сборка, в ней лежит абстрактный класс Maybe, у него два наследника, Success и Failure. Отдельная сборка и интёрнал конструктор нужны, чтобы гарантировать — наследников всегда будет только два.

Тогда метод будет выглядеть вот так:

Фишка подхода в том, что мы закрепляем возможную неудачу типом возвращаемого значения. И теперь наши гарантии будет обеспечивать уже компилятор.

Читайте также:  Почему не работает капсульная кофемашина дольче густо

Использовать код можно например так:

Здесь не так много плюсов, но они очень увесистые.

  • Гарантии, надежность. Есть только один способ обойти нашу защиту — взять, и руками скастить результат к Success. Но это я не знаю, кем надо быть.
  • Мы легко можем протаскивать необходимую информацию об ошибке через класс Failure. Здесь много чего можно наинженерить, но главное — возможности для этого есть. Правда это будет уже не Maybe, а Result монада, но какая разница.
  • Вербозность. По коду, который отдаёт Maybe сразу понятно, что все может пойти не по плану.
  • Сам тип Maybe может быть один, универсальный на весь проект.
  • Подход отлично масштабируется на асинхронный код.
  • Легко разделять ответственность — мы можем сделать методы, которые готовы работать с Maybe, и метода, которые не готовы. Так мы построим приложение, в котором четко, на уровне типов разделена ответственность по обработке ошибок.

А минусы — паршивые.

  • Это совсем не идоматично. Твои коллеги не будут ожидать такие штуки, для C# такая практика — довольно спорная.
  • Костыльная реализация. Мы эмулируем Discriminated Union из фп с помощью наследования, но это именно что эмуляция. Поэтому компилятор не будет нам верить, что есть всего два возможных наследника, а будет плевать в нас варнингами, что наш switch-expression не обработал все возможные случаи.
  • Все ещё не полная надежность.
  • Переусложнение кода, не всегда оправданное.

Короче, монады хороши с логической точки зрения, но их реализация в текущем сишарпе выглядит ну очень некрасиво и непривычно. Это само по себе — большая проблема. Но она скоро решится — разработчики языка обещают поставить в одной из будущих версий Discriminated Unions — именно с помощью таких конструкций монады реализованы во всяких F#-ах или Haskell-ях. Код с монадами перестанет быть уродливым и запутанным, но остается проблема с тем, что подход для шарпистов непривычен. Кроме того весь уже написанный код набит исключениями, и переезжать никто не будет, а куча разных способов решать одинаковые проблемы в одном проект — это совсем не хорошая идея.

Да, у меня в статье Maybe представлена исключительно как концепт. У неё есть отличные реализации в виде библиотек. В случае, если нужно передать информацию об ошибке, используется монада Either/Result. Для которой так же существуют сторонние решения.

Способов борбы с исключениями несколько, не очень понятно, когда и какой использовать.

Если мне не нужно знать, что там за ошибка, сам случай не сверх критичный, у меня C#8+ со включенным nullable, у всех пользователей кода тоже — я бы использовал SomeOrDefault. Если Nullable нету, тогда tryPattern. Если момент критичный, тогда Maybe.

Если у меня нет nullable, и кейс асинхронный — значит try pattern и someOrDefault мне не пойдет, и тогда я бы тоже взял Maybe.

Соответственно, если хотим передать данные об ошибке, тогда лучше использовать Result монаду.

Exception хорошо подходит для случаев:

  • Когда у тебя есть модуль, в нем произошла ошибка, и это значит что с этим модулем больше работать нельзя (например сторонний сервис упал). Выплевываем исключение, ловим его где то сверху, уведомляем все заинтересованные части системы, что сервис сдох.
  • Когда приложение не может продолжать свою работу. Например у нас десктопный софт, который является тонким клиентом, а сеть пропала. Бахаем ексепшн, ловим, говорим «извините», закрываемся.
  • Когда понятия не имеешь, что делать в случае ошибки, да и на проект тебе насрать — тогда бы я тоже взял Exception.

Самый увесистый минус Ecxeption лежит даже не в механике их работы, он в культуре использования. Процессы разработки часто устроены так, что человек понятия не имеет, что делать если операция провалилась. И он делает что, он создает исключение, выбрасывает его, и полностью снимает ответственность как с себя, так и с модуля, который он делает. Этот подход страусов, а не инженеров. Видишь проблему, запихиваешь башку в песок, и делаешь вид, что все идёт окей.

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

Это поднимает куда более серьезную проблему. Ладно, подход с исключениями технически несовершенен, но даже если и был бы, есть штука, куда более несовершенная. Программисты. Человеческий фактор. Я вот пришел сюда, такой умный, начал учить как обрабатывать ошибки, а потом заглянул в код своих проектов, и везде вижу одно и то же- мой класс, как разработчика, недостаточно высок, я постоянно не понимаю, как разруливать исключительные ситуации. Я их игнорирую, логгирую, и прячу. Кроме тех мест, где они кому-то уже навредили, и меня заставили именно там все продумать. И никакие технические возможности языка не заставят меня продумывать все.

Но. Они заставят продумывать чуть больше, может быть, на 5%, может на 1, может на 10. И это вообще единственный способ хоть как то уменьшать влияние человеческого фактора. Поэтому, я не вижу причин, чтобы отказываться от тех же монад или гарантированно обрабатываемых исключений.

Я привел четыре концептуальных подхода к работе с ошибками, но на деле их намного больше. Например приходит в голову подход в Go — отдавать из функций кортеж (результат*ошибка). Как по мне- очень спорный способ, но я открыт к дискуссии. Делитесь мыслями в комментариях, какие ещё у нас есть варианты, и в чем их преимущество.

Код примеров лежит здесь.

На правах рекламы

Подыскиваете виртуальный сервер для отладки проектов, сервер для разработки и размещения? Вы точно наш клиент 🙂 Посуточная тарификация серверов самых различных конфигураций, антиDDoS.

Источник

Оцените статью