Упражнение. Перехват конкретных типов исключений

Завершённый

Ранее в этом модуле вы узнали, что объекты исключений, пойманные приложением 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 свойство можно использовать для получения метода, вызывающего текущее исключение.

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

Замечание

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

Доступ к свойствам объекта исключения

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

  1. Обновите файл 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);
    }
    
  2. Чтобы просмотреть код, сделайте минуту.

    Это тот же код, который вы видели в предыдущем уроке (код решения для задачи). Вы знаете, что исключение выбрасывается во время WriteMessage метода. Вы также знаете, что исключение поймано в методе Process1 . Этот код будет использоваться для проверки объектов исключений и определенных типов исключений.

  3. Выполните следующие действия, чтобы обновить метод Process1.

    static void Process1()
    {
        try
        {
            WriteMessage();
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Exception caught in Process1: {ex.Message}");
        }
    }
    
  4. Пройдите минуту, чтобы проверить обновления.

    Обратите внимание, что обновленное условие catch ловит экземпляр класса Exception в объекте с именем ex. Также обратите внимание, что ваш метод Console.WriteLine() использует ex для доступа к свойству Message объекта и вывода сообщения об ошибке в консоль.

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

    Как правило, следует перехватывать только те исключения, от которых ваш код знает, как восстановиться. Поэтому предложение catch должно указывать аргумент объекта, производный от System.Exception. Тип исключения должен быть как можно более конкретным. Это помогает избежать перехвата исключений, которые обработчик исключений не может устранить. Вы обновите код, чтобы поймать определенный тип исключения позже в этом упражнении.

  5. В меню "Файл" выберите Сохранить.

  6. Установите точку останова в следующей строке кода:

    Console.WriteLine($"Exception caught in Process1: {ex.Message}");
    
  7. В меню "Запуск" выберите "Начать отладку"

    Выполнение кода должно приостановиться в точке останова.

  8. Наведите указатель мыши на ex.

    Обратите внимание, что IntelliSense отображает те же свойства исключений, которые вы изучили ранее.

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

    Обратите внимание, что исключение является типом System.DivideByZeroException исключения, а Message свойство имеет значение Attempted to divide by zero..

    Обратите внимание, что StackTrace свойство сообщает метод и номер строки, в котором произошла ошибка, а также последовательность вызовов методов (и номеров строк), которые привели к ошибке.

  10. На панели инструментов отладки нажмите кнопку "Продолжить".

  11. Минуту, чтобы проверить выходные данные консоли.

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

    ∞
    Exception caught in Process1: Attempted to divide by zero.
    Exit program
    

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

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

  1. Выполните следующие действия, чтобы обновить метод Process1.

    static void Process1()
    {
        try
        {
            WriteMessage();
        }
        catch (DivideByZeroException ex)
        {
            Console.WriteLine($"Exception caught in Process1: {ex.Message}");
        }
    }
    
  2. Сохраните код и запустите сеанс отладки.

  3. Обратите внимание, что обновленное приложение сообщает те же сообщения в консоль.

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

  4. Чтобы создать другой тип исключения, обновите 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;
        }
    }
    
  5. Обратите внимание на использование инструкции checked .

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

    • Если исходный тип больше целевого типа, то исходное значение усечено путем отмены его "дополнительных" наиболее значимых битов. Результат затем обрабатывается как значение целевого типа.

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

    • Если исходный тип совпадает с типом назначения, то исходное значение рассматривается как значение целевого типа.

    Замечание

    Вычисления целочисленного типа, не находящиеся внутри checked блока кода, обрабатываются так, как если бы они находятся в блоке unchecked кода.

  6. Сохраните код и запустите сеанс отладки.

  7. Обратите внимание, что новый тип исключения перехватывается catch предложением в инструкциях верхнего уровня, а не внутри Process1 метода.

    Приложение выводит следующие сообщения в консоль:

    ∞
    An exception has occurred
    Exit program
    

    Замечание

    Блок catch в Process1 не выполняется. Это поведение, которое вы хотели. Перехватывать только исключения, которые ваш код готов обработать.

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

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

  1. Выполните следующие действия, чтобы обновить метод 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;
        }
    }
    
  2. Установите точку останова внутри метода в следующей строке WriteMessage() кода:

    Console.WriteLine(float1 / float2);
    
  3. Сохраните код и запустите сеанс отладки.

  4. Выполняйте ваш код построчно и обратите внимание на то, что происходит после того, как ваш код обрабатывает первое исключение.

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

  5. На минуту рассмотрим, как можно управлять несколькими исключениями, а также когда или почему код может не управлять несколькими исключениями.

    Вы узнали ранее в этом модуле, что исключения должны обрабатываться как можно ближе к месту их возникновения. Учитывая это, вы можете обновить 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}");
        }  
    }
    
  6. При каких условиях нежелательно перехватывать последующие исключения?

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

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

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

  1. Очистите точки останова и замените содержимое файла 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);
        }
    }
    
  2. Чтобы просмотреть этот код, сделайте минуту.

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

    Обратите внимание, что код использует int.Parse метод для преобразования строковых значений input в целые числа. Код int.Parse помещается в try блок кода.

  3. Задайте точку останова в следующей строке кода:

    int numValue = 0;
    
  4. Сохраните код и запустите сеанс отладки.

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

Обзор

Ниже приведены некоторые важные моменты, которые следует помнить из этого урока:

  • Условие catch должно быть настроено для перехвата конкретного типа исключения. Например, DivideByZeroException тип исключения.
  • К свойствам объекта исключения можно получить доступ в блоке catch . Например, свойство Message можно использовать для информирования пользователя приложения о проблеме.
  • Можно указать два или более catch условий, если необходимо перехватывать несколько типов исключений.