Упражнение. Перехват конкретных типов исключений
Ранее в этом модуле вы узнали, что объекты исключений, пойманные приложением C#, являются экземплярами класса исключений. Как правило, ваш код будет catch одним из следующих:
- Объект исключения, который является экземпляром
System.Exceptionбазового класса. - Объект исключения, который является экземпляром типа исключения, наследуемого от базового класса. Например, экземпляр
InvalidCastExceptionкласса.
Проверка свойств исключения
System.Exception — базовый класс, от которому наследуются все производные типы исключений. Каждый тип исключения наследует от базового класса через определенную иерархию классов. Например, иерархия классов для InvalidCastException выглядит так:
Object
Exception
SystemException
InvalidCastException
Большинство классов исключений, наследуемых от Exception, не добавляют никаких дополнительных функций; они просто наследуются от Exception. Таким образом, изучение свойств Exception класса позволяет понять большинство исключений и как можно использовать исключение в коде.
Ниже приведены свойства Exception класса:
-
Данные:
Dataсвойство содержит произвольные данные в парах "ключ-значение". -
HelpLink:
HelpLinkсвойство можно использовать для хранения URL-адреса (или URN) в файле справки, который предоставляет подробные сведения о причине исключения. -
HResult:
HResultсвойство можно использовать для доступа к закодированному числовом значению, назначенному определенному исключению. -
InnerException:
InnerExceptionсвойство можно использовать для создания и сохранения ряда исключений во время обработки исключений. -
Сообщение: свойство
Messageсодержит сведения о причине исключения. -
Источник:
Sourceсвойство можно использовать для доступа к имени приложения или объекта, вызывающего ошибку. -
StackTrace:
StackTraceсвойство содержит трассировку стека, которую можно использовать для определения места возникновения ошибки. -
TargetSite:
TargetSiteсвойство можно использовать для получения метода, вызывающего текущее исключение.
Это нормально, если вы чувствуете себя немного перегруженным этим изучением свойств исключений, базовых классов и наследования. Не беспокойтесь, перехват исключений в коде и доступ к свойствам исключения проще, чем объяснить, как работают исключения и свойства исключений.
Замечание
В этом модуле вы будете сосредоточиться на использовании свойства сообщения исключения, чтобы сообщить об исключении в пользовательском интерфейсе приложения.
Доступ к свойствам объекта исключения
Теперь, когда вы понимаете объекты исключений и их свойства, пришло время начать программирование.
Обновите файл Program.cs следующим образом:
try { Process1(); } catch { Console.WriteLine("An exception has occurred"); } Console.WriteLine("Exit program"); static void Process1() { try { WriteMessage(); } catch { Console.WriteLine("Exception caught in Process1"); } } static void WriteMessage() { double float1 = 3000.0; double float2 = 0.0; int number1 = 3000; int number2 = 0; Console.WriteLine(float1 / float2); Console.WriteLine(number1 / number2); }Чтобы просмотреть код, сделайте минуту.
Это тот же код, который вы видели в предыдущем уроке (код решения для задачи). Вы знаете, что исключение выбрасывается во время
WriteMessageметода. Вы также знаете, что исключение поймано в методеProcess1. Этот код будет использоваться для проверки объектов исключений и определенных типов исключений.Выполните следующие действия, чтобы обновить метод
Process1.static void Process1() { try { WriteMessage(); } catch (Exception ex) { Console.WriteLine($"Exception caught in Process1: {ex.Message}"); } }Пройдите минуту, чтобы проверить обновления.
Обратите внимание, что обновленное условие
catchловит экземпляр классаExceptionв объекте с именемex. Также обратите внимание, что ваш методConsole.WriteLine()используетexдля доступа к свойствуMessageобъекта и вывода сообщения об ошибке в консоль.catchХотя предложение можно использовать без аргументов, этот подход не рекомендуется. Если аргумент не указан, то перехватываются все типы исключений, и невозможно различить между ними.Как правило, следует перехватывать только те исключения, от которых ваш код знает, как восстановиться. Поэтому предложение
catchдолжно указывать аргумент объекта, производный отSystem.Exception. Тип исключения должен быть как можно более конкретным. Это помогает избежать перехвата исключений, которые обработчик исключений не может устранить. Вы обновите код, чтобы поймать определенный тип исключения позже в этом упражнении.В меню "Файл" выберите Сохранить.
Установите точку останова в следующей строке кода:
Console.WriteLine($"Exception caught in Process1: {ex.Message}");В меню "Запуск" выберите "Начать отладку"
Выполнение кода должно приостановиться в точке останова.
Наведите указатель мыши на
ex.Обратите внимание, что IntelliSense отображает те же свойства исключений, которые вы изучили ранее.
Чтобы просмотреть сведения, описывающие объект
exисключения, займет минуту.Обратите внимание, что исключение является типом
System.DivideByZeroExceptionисключения, аMessageсвойство имеет значениеAttempted to divide by zero..Обратите внимание, что
StackTraceсвойство сообщает метод и номер строки, в котором произошла ошибка, а также последовательность вызовов методов (и номеров строк), которые привели к ошибке.На панели инструментов отладки нажмите кнопку "Продолжить".
Минуту, чтобы проверить выходные данные консоли.
Обратите внимание, что свойство исключения
Messageвключается в выходные данные, созданные приложением:∞ Exception caught in Process1: Attempted to divide by zero. Exit program
Перехват определенного типа исключения
Теперь, когда вы знаете тип исключения для перехвата, вы можете обновить catch предложение для обработки этого конкретного типа исключения.
Выполните следующие действия, чтобы обновить метод
Process1.static void Process1() { try { WriteMessage(); } catch (DivideByZeroException ex) { Console.WriteLine($"Exception caught in Process1: {ex.Message}"); } }Сохраните код и запустите сеанс отладки.
Обратите внимание, что обновленное приложение сообщает те же сообщения в консоль.
Несмотря на то, что сообщаемые сообщения одинаковы, существует важное различие. Метод
Process1будет перехватывать только исключения определенного типа, который он готов обрабатывать.Чтобы создать другой тип исключения, обновите
WriteMessageметод следующим образом:static void WriteMessage() { double float1 = 3000.0; double float2 = 0.0; int number1 = 3000; int number2 = 0; byte smallNumber; Console.WriteLine(float1 / float2); // Console.WriteLine(number1 / number2); checked { smallNumber = (byte)number1; } }Обратите внимание на использование инструкции
checked.При выполнении вычислений целочисленных типов, которые назначают значение одного целочисленного типа другому целочисленному типу, результат зависит от контекста проверки переполнения. В контексте
checkedпреобразование завершается успешно, если исходное значение находится в диапазоне целевого типа. В противном случае выбрасываетсяOverflowException. В незаверяемом контексте преобразование всегда выполняется успешно и выполняется следующим образом:Если исходный тип больше целевого типа, то исходное значение усечено путем отмены его "дополнительных" наиболее значимых битов. Результат затем обрабатывается как значение целевого типа.
Если исходный тип меньше конечного типа, то исходное значение расширяется с помощью знака или нуля, чтобы оно было того же размера, как целевой тип. Расширение знака используется, если исходный тип имеет знак; расширение нулём используется, если исходный тип без знака. Результат затем обрабатывается как значение целевого типа.
Если исходный тип совпадает с типом назначения, то исходное значение рассматривается как значение целевого типа.
Замечание
Вычисления целочисленного типа, не находящиеся внутри
checkedблока кода, обрабатываются так, как если бы они находятся в блокеuncheckedкода.Сохраните код и запустите сеанс отладки.
Обратите внимание, что новый тип исключения перехватывается
catchпредложением в инструкциях верхнего уровня, а не внутриProcess1метода.Приложение выводит следующие сообщения в консоль:
∞ An exception has occurred Exit programЗамечание
Блок
catchвProcess1не выполняется. Это поведение, которое вы хотели. Перехватывать только исключения, которые ваш код готов обработать.
Перехват нескольких исключений в блоке кода
На этом этапе может возникнуть вопрос о том, что происходит при возникновении нескольких исключений в одном блоке кода. Будет ли ваш код обрабатывать каждое исключение по мере их возникновения?
Выполните следующие действия, чтобы обновить метод
WriteMessage.static void WriteMessage() { double float1 = 3000.0; double float2 = 0.0; int number1 = 3000; int number2 = 0; byte smallNumber; Console.WriteLine(float1 / float2); Console.WriteLine(number1 / number2); checked { smallNumber = (byte)number1; } }Установите точку останова внутри метода в следующей строке
WriteMessage()кода:Console.WriteLine(float1 / float2);Сохраните код и запустите сеанс отладки.
Выполняйте ваш код построчно и обратите внимание на то, что происходит после того, как ваш код обрабатывает первое исключение.
При возникновении первого исключения элемент управления передается первому
catchпредложению, которое может обрабатывать исключение. Код, создающий второе исключение, никогда не исполняется. Это означает, что некоторые из кода никогда не выполняются. Это может привести к серьезным проблемам.На минуту рассмотрим, как можно управлять несколькими исключениями, а также когда или почему код может не управлять несколькими исключениями.
Вы узнали ранее в этом модуле, что исключения должны обрабатываться как можно ближе к месту их возникновения. Учитывая это, вы можете обновить
WriteMessageметод для перехвата исключений с помощью собственногоtry-catch. Рассмотрим пример.static void WriteMessage() { double float1 = 3000.0; double float2 = 0.0; int number1 = 3000; int number2 = 0; byte smallNumber; try { Console.WriteLine(float1 / float2); Console.WriteLine(number1 / number2); } catch (DivideByZeroException ex) { Console.WriteLine($"Exception caught in WriteMessage: {ex.Message}"); } checked { smallNumber = (byte)number1; } }Можно также обернуть код, вызывающий
OverflowException, в отдельныйtry-catchвнутри методаWriteMessage().checked { try { smallNumber = (byte)number1; } catch (OverflowException ex) { Console.WriteLine($"Exception caught in WriteMessage: {ex.Message}"); } }При каких условиях нежелательно перехватывать последующие исключения?
Рассмотрим случай, когда метод (или блок кода) завершает процесс двух частей. Предположим, что вторая часть процесса зависит от завершения первой части. Если первая часть процесса не может завершиться успешно, нет смысла продолжать работу со второй частью процесса. В этом случае часто лучше предоставить пользователю сообщение, объясняющее условие ошибки, не пытаясь выполнить оставшиеся части более крупного процесса.
Перехват отдельных типов исключений в блоке кода
Существуют случаи, когда изменения в данных могут вызвать различные типы исключений.
Очистите точки останова и замените содержимое файла Program.cs следующим кодом:
// inputValues is used to store numeric values entered by a user string[] inputValues = new string[]{"three", "9999999999", "0", "2" }; foreach (string inputValue in inputValues) { int numValue = 0; try { numValue = int.Parse(inputValue); } catch (FormatException) { Console.WriteLine("Invalid readResult. Please enter a valid number."); } catch (OverflowException) { Console.WriteLine("The number you entered is too large or too small."); } catch(Exception ex) { Console.WriteLine(ex.Message); } }Чтобы просмотреть этот код, сделайте минуту.
Во-первых, код создает строковый массив с именем
inputValues. Данные в массиве предназначены для представления входных значений, введенных пользователем, которому было показано ввести числовые значения. В зависимости от введенного значения могут возникать различные типы исключений.Обратите внимание, что код использует
int.Parseметод для преобразования строковых значений input в целые числа. Кодint.Parseпомещается вtryблок кода.Задайте точку останова в следующей строке кода:
int numValue = 0;Сохраните код и запустите сеанс отладки.
Проходите по коду, выполняя его построчно, и обратите внимание, что обрабатываются разные типы исключений.
Обзор
Ниже приведены некоторые важные моменты, которые следует помнить из этого урока:
- Условие
catchдолжно быть настроено для перехвата конкретного типа исключения. Например,DivideByZeroExceptionтип исключения. - К свойствам объекта исключения можно получить доступ в блоке
catch. Например, свойствоMessageможно использовать для информирования пользователя приложения о проблеме. - Можно указать два или более
catchусловий, если необходимо перехватывать несколько типов исключений.