C# — В чём ошибка кода на C#


Перехват исключений

Принимая во внимание, что .NET Framework включает большое количество предопределенных классов исключений, возникает вопрос: как их использовать в коде для перехвата ошибочных условий? Для того чтобы справиться с возможными ошибочными ситуациями в коде C#, программа обычно делится на блоки трех разных типов:

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

Блоки catch инкапсулируют код, который обрабатывает ошибочные ситуации, происходящие в коде блока try. Это также удобное место для протоколирования ошибок.

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

Try и catch

Основу обработки исключительных ситуаций в C# составляет пара ключевых слов try и catch. Эти ключевые слова действуют совместно и не могут быть использованы порознь. Ниже приведена общая форма определения блоков try/catch для обработки исключительных ситуаций:

где ExcepType — это тип возникающей исключительной ситуации. Когда исключение генерируется оператором try, оно перехватывается составляющим ему пару оператором catch, который затем обрабатывает это исключение. В зависимости от типа исключения выполняется и соответствующий оператор catch. Так, если типы генерируемого исключения и того, что указывается в операторе catch, совпадают, то выполняется именно этот оператор, а все остальные пропускаются. Когда исключение перехватывается, переменная исключения exOb получает свое значение. На самом деле указывать переменную exOb необязательно. Так, ее необязательно указывать, если обработчику исключений не требуется доступ к объекту исключения, что бывает довольно часто. Для обработки исключения достаточно и его типа.

Следует, однако, иметь в виду, что если исключение не генерируется, то блок оператора try завершается как обычно, и все его операторы catch пропускаются. Выполнение программы возобновляется с первого оператора, следующего после завершающего оператора catch. Таким образом, оператор catch выполняется лишь в том случае, если генерируется исключение.

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

Данный простой пример наглядно иллюстрирует обработку исключительной ситуации при делении на 0 (DivideByZeroException), а так же пользовательскую ошибку при вводе не числа (FormatException).

Последствия неперехвата исключений

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

Например, если убрать из предыдущего примера исключение FormatException, то при вводе неккоректной строки, IDE-среда VisualStudio выдаст предупреждающее сообщение:

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

C# — В чём ошибка кода на C#

На 32-м уроке по C# мы начнем обзор обработки исключений. При возникновении непредвиденного события необработанные исключения приводят к аварийному завершению работы программы. Правильная обработка исключений позволяет выйти из ошибки без поломки программы или даже сеанса системы.

Что такое исключение?

Исключение — это ошибка, возникающая вемя выполнения программы. Как правило, исключение описывает непредвиденное событие. Например, исключение возникает, если программа запрашивает больше памяти, чем может предоставить операционная система. Это исключение называется исключением нехватки памяти.

Иерархия классов исключений

При возникновении исключения нормальная обработка кода прекращается. Создается объект, содержащий информацию, относящуюся к ошибке. Этот объект является экземпляром класса исключений. Самым основным из классов исключений является Exception, определенный в пространстве имен System.

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

Существует множество системных исключений, которые могут возникать или выбрасываться при возникновении проблемы. Они вызывают создание объекта на основе его собственного специализированного класса исключений, производного от SystemException. Примеры включают в себя:

  • ArithmeticException. Выбрасывается при возникновении ошибки во время арифметической операции, приведения или преобразования.
  • DivideByZeroException. Возникает при попытке разделить значение на ноль. Является более специализированной версией исключения выше.
  • OverflowException. Выбрасывается, когда ошибка возникает во время арифметической операции или во время литья или преобразования, потому что результирующее значение слишком велико или мало. Исключение OverflowException является производным от исключения ArithmeticException.
  • OutOfMemoryException. Выбрасывается, когда доступной памяти недостаточно для продолжения выполнения.

Приведенный выше список дает небольшую выборку классов исключений в иерархии. Гораздо более полный список можно найти на странице иерархии системных исключений веб-сайта Microsoft MSDN.

Исключения приложений — это те, которые вы определяете. Они могут быть универсальными, например WordProcessorException, или специализированными, например LeftMarginTooSmallException. Конечно, ни одно из этих исключений не существует в .NET framework; они должны быть созданы перед использованием. Исключения приложений не будут обсуждаться до следующей статьи, в которой исследуются программные исключения.

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

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

Основной блок Try / Catch

C# предоставляет структуру кода, известную как блок try / catch, которая позволяет обрабатывать исключения. Основной блок try / catch имеет два элемента. Раздел try содержит одну или несколько выполняемых команд, удерживаемых в символах фигурной скобки < и >. Раздел catch содержит код для выполнения, если во время обработки раздела try происходит исключение. Основной синтаксис выглядит следующим образом:

При использовании этого базового синтаксиса любое возникающее исключение приводит к тому, что код в блоке try остается, а код в блоке catch выполняется. Блок catch может использоваться для различных целей, включая изящное восстановление после ошибки, ведение журнала или представление подробных сведений о проблеме, а также освобождение ресурсов, таких как подключения к базе данных или открытые файлы. После завершения выполнения блока catch или если в блоке try не возникает никаких исключений, программа продолжает выполнение следующей инструкции после структуры try / catch.

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

Извлечение информации об исключении

Как написано выше, создание исключения включает в себя создание объекта исключения. Объект имеет свойства, содержащие информацию, описывающую ошибку. Это может быть прочитано кодом в блоке catch путем добавления объявления исключения к оператору catch. Следующий пример расширяет предыдущий код, объявляя объект наиболее обобщенного класса исключений. Все исключения будут пойманы, но теперь информация об исключении может быть использована при создании сообщения об ошибке.

Приведенный выше пример улавливает любое исключение и заполняет объект класса Exception . Свойство объекта Message используется для вывода описания ошибки. Это одно из нескольких свойств, предоставляемых всеми классами исключений. Некоторые из самых полезных свойств являются:

  • Message. Строка, содержащая описание исключения.
  • Source. Строка, содержащая имя программы или объекта, вызвавшего исключение.
  • TargetSite. Объект, содержащий сведения о методе, вызвавшем исключение.
  • StackTrace. Строка, содержащая полный стек вызовов, которые привели к исключению. Эта строка позволяет программисту просматривать каждый вызов метода, выполненный до возникновения исключения. Это особенно полезно во время тестирования и отладки.
  • InnerException. Когда одно исключение возникает как прямой результат другого, начальное исключение может содержаться в этом свойстве. Внутреннее исключение содержит все стандартные свойства, включая, возможно,еще одно внутреннее исключение. Если нет внутреннего исключения, это свойство имеет значение null.

Примечание: более специализированные типы исключений включают дополнительную релевантную информацию. Например, исключение ArgumentException включает свойство ParamName, детализирующее рассматриваемый параметр.

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

До сих пор описанные примеры включали код для перехвата всех исключений. Иногда вы хотите поймать только определенный тип исключения, так как различные ошибки могут быть обработаны по-разному. Для того, чтобы поймать более специализированные исключение, класс exception определяется по имени в операторе catch. В следующем примере этот метод используется только для перехвата деления на ноль. Любое другое исключение остается необработанным.

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

Примечание: если достаточно поймать тип исключения и нет необходимости опрашивать свойства исключения, то нет необходимости включать имя переменной для объекта исключения. Catch в приведенном выше примере может быть сокращен до catch (DivideByZeroException) в такой ситуации.

Перехват нескольких исключений

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

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

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

Цукерберг рекомендует:  Контекстное меню в браузере средствами HTML5

Блок Try / Catch / Finally

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

C# определяет блок добавления, который может быть добавлен в конец структуры try / catch. Это последний блок. Код в этом разделе гарантированно выполняется после блока try / catch, даже если возникает исключение или блок try содержит оператор return.

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

C# — В чём ошибка кода на C#

Доброго времени суток! Сегодня мы поговорим о том, как обрабатывать ошибки в программах, написанных на языке C#.

Сразу хочу отметить, что ошибки в программах, можно условно разделить на две группы: ошибки времени сборки программы (на этапе компиляции) и ошибки времени выполнения программы. В этом уроке разговор будет об ошибках времени выполнения!

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

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

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

А работает это так, если в блоке try, не произошла ошибка, то блок catch не выполняется, а управление передается дальше, т.е. выполняется следующий за блоком catch оператор. А вот если внутри блока try произошла ошибка, то выполнение этого блока прерывается, и управление передается в блок catch, а уже потом выполняются следующие за этим блоком операторы.

На практике, после одного блока try, может быть несколько блоков catch, для отдельной обработки ошибок разных типов.

Давайте модифицируем код программы из предыдущего урока, добавим контроль и обработку ошибок, которые могут возникнуть при вводе пользователем некорректном данных.

Теперь, если пользователь введет некорректные данные вместо целого числа, то произошедшая ошибка будет обработана. Если вставить этот код в метод «Main», собрать и запустить программу, то можно будет увидеть следующее поведение:

Пользователь ввел корректные данные

Пользователь ввел некорректные данные

И так, в этом уроке была представлена базовая информация, касающаяся механизма обработки ошибок (исключительных ситуаций) в языке C#. В следующем уроке, мы поговорим о создании и использовании собственных методов (функций), а к теме обработки ошибок, вернемся еще и не раз!

Новичок в C# / Правильно ли я написал вывод ошибки?

На счет else, тоже сам допер, что он по сути не нужен.

На счет заменить while на if в блоке try, согласен с вами, так как по сути он там не нужен, потому что есть первый while, в котором в конце я все равно проверяю переменную number_user1 на то чтобы она была равна от 1 до 5 и если это не так, то цикл повторяется.

поменьше текста — будет красивее

Добрый день.
0. Блок if-else надо помещать в блок try. Так как зачем сравнивать если произошла ошибка?
1. В вашем случае лучше использовать ReadKey, так как по условию один символ от 1 до 5.
2. Желательно использовать блок finally, но в вашем случае не обязательно.
3. На мой взгляд много лишних проверок

Обработка ошибок в принципе реализована правильно, но надо помнить, что это достаточно тяжелая операция и в данном случае избыточная. В данном случае можно проверять через TryParse. Я бы решил так —

C# Урок 1. Установка Visual Studio Code

Че так долго = P.S. Словом, разлил газировку на ноут, был в депрессии, ай пофиг, начали.

1. C чего начать

Для работы с текстом вам нужен текстовый редактор (World), для обработки фотографий вам нужен графический редактор (Photoshop), для программирования требуется IDE(интегрированная среда разработки).

IDE – это комплекс программных средств для разработки ПО, такие как

a. Текстовой редактор (Ну это просто, это там, где мы пишем код)

b. Компилятор/интерпретатор (переводит наш код в машинный код, способный к выполнению процессором)


c. Средства автоматизации сборки (Помогает собрать наш проект. К примеру, в исполняемый .exe файл)

d. Отладчик/Debugger (Можно ставить всякие точки остановки (breakpoint и watch) чтобы отслеживать, что выполняет код на данной строчке кода)

Под C# основной IDE является Visual Studio (Community является бесплатной, и в принципе там есть практически всё). Но(!) в наших уроках мы будем писать в Visual Studio Code (редактор кода).

Итак, определились. Скачиваем Visual Studio Code ( https://code.visualstudio.com/ )

Нажимаем на нужную кнопку “Windows” и скачиваем установщик. Там всё стандартно, ставим галочку «Добавить в Path».

А затем скачиваем .NET Core SDK ( https://dotnet.microsoft.com/download ). Просто ждем установки, после установки вроде потребуется перезагрузить ПК, я уже не помню.

Далее запускаем VSCode, переходим на вкладку Extension и устанавливаем расширение для VSCode ->C#. Для этого нужно в поиске расширений просто написать C#

Далее создаем какую-нибудь папку, где у нас будет проект, и открываем эту папку в VSCode File->Open Folder…

Далее нам нужно создать консольное приложение на языке C#. Переходим по этой ссылке, https://docs.microsoft.com/en-us/dotnet/core/tools/dotnet-ne. , и находим среди всех команд ту что нужно.

Далее в VSCode выбираем меню Terminal->New Terminal и пишем туда команду

dotnet new console

Ждем и затем пишем

чтобы скачались нужные компоненты (требуется один раз). Далее пишем

и в терминале происходит запуск нашей программы. Выглядит не Айс, поэтому идет до конца.

Запускаем нашу программу Debug-Start Debugging. Выбираем (.NET Core), чем будем компилировать наш код и открывается окно настройки.

Обработка исключений для C#

View more Tutorials:

1- Что такое Exception?

Сначала давайте посмотрим на следующий иллюстративный пример:

В этом примере есть часть кода c ошибкой, которая получается из-за деления на 0. Деление на 0 вызывает исключение: DivideByZeroException

Мы будем модифицировать код примера выше:

2- Иерархия исключений

Exception

Основной класс всех исключений.

SystemException

Основной класс всех исключений генерированных во время запуска программы.

IndexOutOfRangeException

Выбрасывается во время запуска при доступе в элемент массива с неправильным индексом.

NullReferenceException

Выбрасывается во время запуска при ссылке на объект null.

AccessViolationException

Выбрасывается во время запуска при ссылке к недействительной памяти.

InvalidOperationException

Выбрасывается методом при недействительном статусе.

ArgumentException

Основной класс всех исключений связанных с аргументом (Argument).

ArgumentNullException

Это подкласс ArgumentException, который выбрасывается методом, не позволяющим передачу аргументов null.

ArgumentOutOfRangeException

Это подкласс ArgumentException, который выбрасывается методом когда аргумент не соответствует рамке позволяющей передачу.

ExternalException

Основной класс для исключений или пришедших из внешней среды.

COMException

Класс расширенный из ExternalException, исключение упаковывает информацию COM.

SEHException

Класс расширенный из ExternalException, ловил все исключения из Win32.

Главное в .NET — Обработка исключение на C#

By Марк Михейлис | November 2015

Добро пожаловать в новую рубрику «Главное в .NET». В этой рубрике вы сможете следите за всем, что творится в мире Microsoft .NET Framework, будь то достижения в C# vNext (в настоящее время C# 7.0), усовершенствования во внутреннем устройстве .NET или события на фронте Roslyn и .NET Core (например, перевод MSBuild в статус ПО с открытым исходным кодом).

Я вел разработки с использованием .NET и писал об этой инфраструктуре с момента объявления о выпуске ее предварительной версии в 2000 г. В этой рубрике я буду писать не просто о новшествах, но и о том, как использовать соответствующие технологии с упором на рекомендации по их применению.

Я живу в Спокане (штат Вашингтон), где являюсь «главным корифеем» в консалтинговой компании сегмента «high-end», которая называется IntelliTect (IntelliTect.com). IntelliTect специализируется на решении трудных задач и великолепно справляется с ними. Я был Microsoft MVP (в настоящее время в области C#) в течении 20 лет и восемь из них занимал должность регионального директора Microsoft. Сегодня я открываю рубрику обсуждением обновленных правил обработки исключений.

В C# 6.0 включили два новых средства обработки исключений. Во-первых, появилась поддержка условий исключения (exception conditions) — возможность предоставлять выражение, которое отфильтровывает исключение, предотвращая его попадание в блок catch до раскрутки стека. Во-вторых, введена поддержка асинхронности в блоке catch — того, что было немыслимо в C# 5.0, хотя именно тогда в язык добавили асинхронность. Кроме того, в последних пяти версиях C# и соответствующих выпусках .NET Framework было много других изменений, которые в некоторых случаях достаточно значимы, чтобы потребовать правок в правилах кодирования на C#. В этом выпуске рубрики я дам обзор этих изменений и представлю обновленные правила кодирования, относящиеся к обработке исключений — их захвату.

Захват исключений: обзор

Как хорошо понятно, генерация исключения определенного типа позволяет механизму захвата (catcher) использовать сам тип исключения для идентификации проблемы. Иначе говоря, не обязательно захватывать исключение и применять выражение switch к сообщению об исключении, чтобы определить, какое действие надо предпринять в связи с этим исключением. Вместо этого C# поддерживает множество блоков catch, каждый из которых нацелен на исключение конкретного типа, как показано на рис. 1.

Рис. 1. Захват исключений разных типов

Когда возникает исключение, управление передается первому блоку catch, способному обработать это исключение. Если с try сопоставлено более одного блока catch, близость совпадение определяется цепочкой наследования (предполагая отсутствие условия исключения C# 6.0), и обработка исключения передается первому catch в этой цепочке. Например, в коде на рис. 2 генерируется исключение типа System.Exception, и оно обрабатывается вторым блоком catch, так как System.InvalidOperationException в конечном счете наследует от System.Exception. Поскольку InvalidOperationException наиболее близко совпадает со сгенерированным исключением, оно будет захвачено блоком catch(InvalidOperationException. ), а не catch(Exception. ), если бы таковой блок был в этом коде.

Блоки catch должны появляться в порядке (вновь предполагая отсутствие условия исключения в C# 6.0) от наиболее специфичных до наиболее универсальных, чтобы избежать ошибки при компиляции. Например, добавление блока catch(Exception. ) до любого другого приведет к ошибке при компиляции, поскольку все предыдущие исключения наследуют от System.Exception в той или иной точке цепочки наследования. Также заметьте, что именованный параметр для блока catch не обязателен. По сути, заключительный catch, к сожалению, допускается даже без указания типа параметра.

Цукерберг рекомендует:  Обзор и установка PHP-фреймворка Codeigniter

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

Сценарий 1 Захваченное исключение не в полной мере идентифицирует проблему, из-за которой возникло это исключение. Например, при вызове System.Net.WebClient.DownloadString с допустимым URL исполняющая среда может сгенерировать System.Net.WebException, если нет сетевого соединения; то же самое исключение генерируется при указании несуществующего URL.

Сценарий 2 Захваченное исключение включает закрытые данные, которые не должны раскрываться выше по цепочке вызовов. Например, в очень ранней версии CLR v1 (фактически в версии pre-alpha) было исключение, сообщающее нечто вроде «Security exception: You do not have permission to determine the path of c:\temp\foo.txt» («Исключение защиты: у вас нет прав на определение пути c:\temp\foo.txt»).

Сценарий 3 Тип исключения слишком специфичен для обработки вызвавшим кодом. Например, исключение System.IO (вроде UnauthorizedAccessException, IOException, FileNotFoundException, DirectoryNotFoundException, PathTooLongException, NotSupportedException или SecurityException, ArgumentException) возникает на сервере при вызове веб-сервиса для поиска почтового кода ZIP.

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

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

Второй вариант при захвате исключения — определять, что вы фактически не в состоянии должным образом обработать его. В этом случае вы захотите повторно сгенерировать точно такое же исключение, отправив его следующему обработчику вверх по цепочке вызовов. Блок catch(InvalidOperationException. ) на рис. 1 как раз и демонстрирует этот вариант. В выражении throw не указана идентификация генерируемого им исключения (присутствует только ключевое слово throw), хотя экземпляр исключения (exception) появляется в области видимости этого блока catch и его можно было бы сгенерировать повторно. Генерация специфического исключения привела бы к обновлению всей информации стека для соответствия новой позиции throw. В итоге вся информация стека, указывающая место вызова, где возникло исходное исключение, была бы утрачена, что сильно затруднило бы диагностику проблемы. Так что, определив, что данный блок catch не может полноценно обработать исключение, это исключение следует генерировать повторно, используя пустое выражение throw.

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

Генерация существующих исключений без замены информации стека

В C# 5.0 был добавлен механизм, позволяющий генерировать ранее сгенерированное исключение, не теряя информации трассировки стека в исходном исключении. Это дает возможность повторно генерировать исключения, например, даже извне блока catch, а значит, без использования пустого выражения throw. Хотя потребность в этом возникает весьма редко, в некоторых случаях исключения обертываются или сохраняются до тех пор, пока поток выполнения программы не выйдет из блока catch. Так, многопоточный код мог бы обертывать исключение в AggregateException. .NET Framework 4.5 предоставляет класс System.Runtime.ExceptionServices.ExceptionDispatchInfo специально для этого случая, для чего используются его статический метод Capture и метод экземпляра Throw. Рис. 2 демонстрирует повторную генерацию исключения без сброса информации трассировки стека или применения пустого выражения throw.

Рис. 2. Использование ExceptionDispatchInfo для повторной генерации исключения

В случае метода ExeptionDispatchInfo.Throw компилятор не интерпретирует его как выражение возврата в том же стиле, в каком это могло бы быть сделано для обычного выражения throw. Например, если бы сигнатура метода возвращала значение, но на самом деле никакого значения по пути кода с ExceptionDispatchInfo.Throw не возвращалось бы, компилятор выдавал бы ошибку, указывающую на отсутствие возвращенного значения. Иногда разработчикам приходится следовать ExceptionDispatchInfo.Throw с выражением return, хотя такое выражение никогда не будет выполнено — вместо этого будет генерироваться исключение.

Захват исключений в C# 6.0

Универсальное правило обработки исключений — избегать захвата тех исключений, которые вы не можете полноценно обработать. Однако, поскольку выражения catch до появления C# 6.0 могли фильтровать исключения лишь по их типу, возможность проверки данных и контекста исключения до раскрутки стека в блоке catch требовали, чтобы этот блок был обработчиком до анализа исключения. К сожалению, после принятия решения о невозможности обработки исключения писать код, который позволяет другому блоку catch в том же контексте обработать исключение, было весьма затруднительно. А повторная генерация того же исключения вынуждает инициировать двухпроходный процесс обработки исключения, который сначала доставляет исключение выше по цепочке вызовов до тех пор, пока не найдется блок, способный его обработать, а затем раскручивает стек вызовов для каждого фрейма в стеке между исключением и позицией захвата.

Вместо того чтобы после генерации исключения раскручивать стек вызовов в блоке catch лишь для повторной генерации исключения из-за того, что при дальнейшем анализе стала понятной невозможность его полноценной обработки, очевидно, было бы предпочтительнее вообще не захватывать это исключение. Начиная с C# 6.0, в блоках catch доступно дополнительное условное выражение. Вместо подбора блока catch только по типу исключения в C# 6.0 введена поддержка проверки по условию. Выражение when позволяет вам предоставлять булево выражение, которое дополнительно фильтрует блок catch и определяет, что он способен обработать исключение, только если условие дает true. Блок System.Web.HttpException на рис. 1 демонстрирует это с помощью оператора сравнения на равенство (equality comparison operator).

Интересный результат условия исключения в том, что, когда указывается это условие, компилятор не принуждает к расстановке блоков catch в порядке цепочки наследования. Например, блок catch для типа System.ArgumentException с сопутствующим условием исключения теперь может появляться до более специфического блока catch для типа System.ArgumentNullException, хотя последний наследует от первого. Это важно, так как позволяет писать специфическое условие исключения, которое сопоставлено универсальному типу исключения, следующему за более специфическим типом исключения (с условием исключения или без него). Поведение в период выполнение остается согласованным с более ранними версиями C#: исключения захватываются первым совпадающим блоком catch. Просто теперь вопрос о том, можно ли считать какой-то блок catch совпадающим, решается на основе комбинации типа и условия исключения, а компилятор вводит порядок относительно только блоков catch, не имеющих условий исключения. Например, catch(System.Exception) с условием исключения может появляться до catch(System.ArgumentException) с условием исключения или без него.

Однако, как только появляется catch без условия исключения, более специфический блок catch [скажем, catch(System.ArgumentNullException)] может быть блокирован, даже если в нем есть условие исключения. Это оставляет программисту свободу в кодировании условий исключений, которые потенциально могут появляться не по порядку, — при этом более ранние условия исключения, захватывающие исключения, предназначенные для более поздних условий, что потенциально может даже сделать последних непреднамеренно недостижимыми. В конечном счете порядок ваших блоков catch аналогичен тому, как вы располагали бы выражения if-else. Когда условие удовлетворяется, все остальные блоки catch игнорируются. Однако в отличие от условий в выражениях if-else все блоки catch должны включать проверку типа исключения.

Обновленные правила обработки исключений

Пример оператора сравнения на рис. 1 тривиален — условие исключения не обязательно должно быть столь простым. Вы могли бы, например, вызывать какой-то метод для проверки условия. Единственное требование в том, что выражение должно быть предикатом — возвращать булево значение. Иначе говоря, вы можете фактически выполнять любой код из цепочки вызовов захвата исключения. Это открывает возможность полного предотвращения повторного захвата и генерации того же исключения; по сути, вы можете достаточно сузить контекст до захвата исключения и захватывать его только в том случае, если оно действительно будет обработано. Таким образом, правило избегать захвата исключений, которые вы не в состоянии полностью обработать, становится реальностью. Любая проверка условий, окружающая пустое выражение throw, вероятно, может служить признаком плохого кода (code smell), и этого следует избегать. Подумайте о добавлении условия исключения вместо использования пустого выражения throw, кроме случая сохранения измененяемого состояния (volatile state) перед завершением процесса.


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

Универсальный блок catch

C# требует, чтобы любой объект, генерируемый кодом, был производным от System.Exception. Однако это требование не универсально для всех языков. Так, C/C++ позволяет генерировать объект любого типа, в том числе управляемые исключения, не производные от System.Exception, или даже элементарные типы вроде int или string. Начиная с C# 2.0, все исключения — производные они от System.Exception или нет — будут передаваться в C#-сборки как производные от System.Exception. Результат заключается в том, что блоки catch для System.Exception будут захватывать все «достоверно обрабатываемые» исключения, не захваченные предшествующими блоками. Однако до C# 1.0, если исключение, не производное от System.Exception, генерировалось при вызове какого-то метода (находящего в сборке, написанной не на C#), то оно не захватывалось блоком catch(System.Exception). По этой причине C# также поддерживает универсальный блок catch (catch< >), который теперь ведет себя идентично блоку catch(System.Exception exception) с тем исключением, что в нем не указывается ни тип, ни имя переменной. Недостаток такого блока в том, что у вас нет экземпляра исключения, к которому вы могли бы обратиться, и нет способа узнать, какие действия следовало бы предпринять. Нельзя даже запротоколировать это исключение или распознать маловероятный случай, где такое исключение безопасно.

На практике вы должны избегать блока catch(System.Exception) и универсального блока catch (далее обобщенно называемых блоком catch для System.Exception), кроме как под предлогом «обработки» исключения простым его протоколированием перед завершением процесса. Следуя универсальному правилу захватывать только те исключения, которые вы можете обработать, было бы слишком самонадеянным писать код, для которого программист объявляет, что этот catch может обрабатывать любые возможные исключения. Во-первых, попытка перечислить всевозможные исключения (особенно в теле Main, где находится самое большое количество выполняемого кода и где контекст скорее всего минимален) — задача неподъемная, кроме как в простейших программах. Во-вторых, существует уйма исключений, которые могут генерироваться неожиданно для вас.

Цукерберг рекомендует:  Красивый слайдер-галерея

До C# 4.0 существовал третий набор исключений из-за поврежденного состояния, при которых программу, как правило, нельзя было восстановить хотя бы в принципе. Этот набор не столь значим, начиная с C# 4.0, но catch(System.Exception) (или универсальный блок catch) на самом деле не будет захватывать такие исключения. (С технической точки зрения, метод можно дополнить System.Runtime.ExceptionServices.HandleProcessCorruptedStateExceptions, чтобы захватывать даже эти исключения, но вероятность того, что вы сумеете в достаточной мере обработать их, крайне низка. Подробнее на эту тему см. по ссылке bit.ly/1FgeCU6.)

Одна техническая деталь, которую стоит отметить в исключениях из-за поврежденного состояния, заключается в том, что при генерации в период выполнения они передаются только через блоки catch для System.Exception. Явно сгенерированное исключение из-за поврежденного состояния, такое как System.StackOverflowException или другое System.SystemException, будут фактически захвачены. Однако генерация такого исключения будет грубейшей ошибкой и на самом деле поддерживается только для обратной совместимости. Современные правила требуют, чтобы вы не генерировали какое-либо из этих исключений (в том числе System.StackOverflowException, System.SystemException, System.OutOfMemoryException, System.Runtime.InteropServices.COMException, System.Runtime.InteropServices.SEHException и System.ExecutionEngineException).

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

Заключение

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

  • избегайте захвата исключений, которые вы не в состоянии полноценно обработать;
  • избегайте скрытия (отбрасывания) исключений, которые вы не полностью обрабатываете;
  • используйте throw для повторной генерации исключения, а не throw внутри блока catch;
  • указывайте в свойстве InnerException исключения-оболочки захваченное исключение, если только это не приводит к раскрытию конфиденциальных данных;
  • подумайте о применении выражения условия вместо повторной генерации исключения после захвата исключения, которое вы не можете обработать;
  • избегайте генерации исключений из условного выражения исключения;
  • будьте осторожны при повторной генерации других исключений;
  • используйте System.Exception и универсальные блоки catch только в редких случаях — при необходимости запротоколировать исключение перед завершением приложения;
  • избегайте отчета об исключении или его протоколирования ниже по стеку вызовов.

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

Заметьте, что многое из этого материала взято из следующего издания моей книги «Essential C# 6.0 (5th Edition)» (Addison-Wesley, 2015), которая доступна сейчас на itl.tc/EssentialCSharp.

Марк Михейлис*(Mark Michaelis) — учредитель IntelliTect, где является главным техническим архитектором и тренером. Почти два десятилетия был Microsoft MVP и региональным директором Microsoft с 2007 года. Работал в нескольких группах рецензирования проектов программного обеспечения Microsoft, в том числе C#, Microsoft Azure, SharePoint и Visual Studio ALM. Выступает на конференциях разработчиков, автор множества книг, последняя из которых — «Essential C# 6.0 (5th Edition)». С ним можно связаться в Facebook (facebook.com/Mark.Michaelis), через его блог (IntelliTect.com/Mark), в Twitter (@markmichaelis) или по электронной почте mark@IntelliTect.com.*

Выражаю благодарность за рецензирование статьи экспертам Кевину Босту (Kevin Bost), Джейсону Питерсону (Jason Peterson) и Мэдсу Торгерсону (Mads Torgerson).

Как получить код ошибки исключения в С#

Когда я вызываю вышеупомянутый метод WMI, я ожидаю получить отказ в доступе. В моем блоке catch я хочу убедиться, что возникшее исключение действительно было для Access Denied. Есть ли способ получить код ошибки для него? Код ошибки Win32 для Acceess Denied — 5. Я не хочу искать сообщение об ошибке для запрещенной строки или что-то в этом роде.

попробуйте это, чтобы проверить исключение и внутреннее для исключения Win32Exception.

Как и в комментариях, вам действительно нужно увидеть, какое исключение действительно бросается, чтобы понять, что вы можете сделать, и в этом случае предпочтение отдается предпочтению только для исключения Exception. Что-то вроде:

Вы должны посмотреть на члены исключенного исключения, особенно .Message и .InnerException .

Я бы также посмотрел, указывает ли документация InvokeMethod на то, выбрал ли он более специализированный класс исключений, чем Exception, — например, исключение Win32Exception, предлагаемое @Preet. Захват и просто просмотр базового класса Exception может оказаться не особенно полезным.

Я предлагаю вам использовать Message Properte из объекта исключения. Как ниже код

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

Этот код был протестирован модулем.

Я не буду слишком беспокоиться о необходимости оценивать и внедрять эффективную практику, когда дело доходит до обработки исключений, управляя каждым ожидаемым типом исключения в своих собственных блоках.

C # — Обработка исключений

Исключением является проблема, возникающая во время выполнения программы. Исключение AC # — это ответ на исключительное обстоятельство, которое возникает во время работы программы, например попытка деления на ноль.

Исключения обеспечивают способ передачи контроля из одной части программы в другую. Обработка исключений C # построена на четырех ключевых словах: try , catch , finally и throw .

  • try — блок try идентифицирует блок кода, для которого активируются определенные исключения. За ним следует один или несколько блоков catch .
  • catch — программа выхватывает исключение с обработчиком исключений в месте в программе, где вы хотите справиться с этой проблемой. Ключевое слово catch указывает на улавливание исключения.
  • finally — блок finally используется для выполнения заданного набора операторов, независимо от того, выбрано или не выбрано исключение. Например, если вы открываете файл, он должен быть закрыт независимо от того, создано ли исключение или нет.
  • throw — программа выдает исключение, когда возникает проблема. Это делается с использованием ключевого слова throw .

Синтаксис

Предполагая, что блок вызывает исключение, метод выбирает исключение, используя комбинацию ключевых слов try и catch . Блок try / catch помещается вокруг кода, который может генерировать исключение. Код внутри блока try / catch называется защищенным кодом, а синтаксис использования try / catch выглядит следующим образом:

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

Классы исключений в C #

Исключения C # представлены классами. Исключительные классы в C # в основном прямо или косвенно получены из класса System.Exception . Некоторые классы исключений, полученные из класса System.Exception, представляют собой классы System.ApplicationException и System.SystemException .

Класс System.ApplicationException поддерживает исключения, создаваемые прикладными программами. Следовательно, исключения, определенные программистами, должны вытекать из этого класса.

Класс System.SystemException является базовым классом для всех предопределенных системных исключений.

В следующей таблице приведены некоторые предопределенные классы исключений, полученные из класса Sytem.SystemException :

Обрабатывает ошибки ввода-вывода.

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

Обрабатывает ошибки, возникающие, когда тип несовместим с типом массива.

Обрабатывает ошибки, возникающие при ссылке на нулевой объект.

Обрабатывает ошибки, возникающие при делении дивиденда на ноль.

Обрабатывает ошибки, возникающие при типизации.

System.OutOfMemoryException

Обрабатывает ошибки, возникающие из-за недостаточной свободной памяти.

System.StackOverflowException

Обрабатывает ошибки, возникающие при переполнении стека.

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

C # обеспечивает структурированное решение обработки исключений в виде блоков try и catch . Используя эти блоки, операторы основной программы отделены от операторов обработки ошибок.

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

Когда приведенный выше код компилируется и выполняется, он производит следующий результат:

Создание пользовательских исключений

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

Когда приведенный выше код компилируется и выполняется, он производит следующий результат:

C# → Ошибки и обработка исключений

Исключение является экземпляром класса, который был явно и неявно унаследован от базового класса System.Exception.

Переменные, объявленные в блоке try выходят из видимости, когда управление предается в блок catch или finally. По окончании блока try, даже если ничего не произошло (ошибка не возникла), управление автоматически передается в блок finally, который должен содержать инструкции для освобождения ресурсов.

При обнаружении ошибки код осуществляет генерацию исключения (создание экземпляра класса исключения) и выдает его следующим образом:

Как только компилятор встречает оператор throw внутри блока try, он немедленно ищет соответствующий блок catch.

Операторы throw не обязательно должны находится в том же методе, что и блок try, блок try продолжает действовать даже при передаче управления другим методам.

Оператор throw может находиться в любом методе, вызванном во время выполнения блока try, — оно не обязано располагаться в том же самом методе, в котором определен блок try

Обработчики исключений для производного класса (IndexOutOfRangeException) должны идти раньше, чем для базового (Exception) (!)

Если обработчик catch записан как: catch < … >то это значит, что он отвечает за любой код (за любое возникшее исключение), в том числе написанным не на C# или не управляемым.

То, что в качестве исключений могут быть переданы только экземпляры класса, производного от System.Exception является требованием C#.

Свойства и методы System.Exception

В коде не нужно генерировать исключения от общего класса System.Exception – он не дает представления о природе ошибочного состояния (!). В иерархии существует два важных класса:

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

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

IOException (пространство имен System.IO) связаны с чтением и записью данных в файл.

StackOverflowException возникает тогда, когда участок памяти отведенный под стек, заполняется до отказа. Переполнение стека может возникнуть например в том случае, когда метод начинает рекурсивно вызвать самого себя. Обычной причиной появления EndOfStreamException является попытка чтения за границами файла. Переполнение OverflowException возникает, например при попытке привести int, содержащий -40 к типу uint в контексте checked.

Вложенные боли try используются по двум причинам:

— с целью изменения типа сгенерированного исключения (использование свойства InnerException, которое содержит ссылку на любое сгенерированное исключение);

— обработка разных исключений в разных участках кода.

Определение собственных классов исключений

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

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