Главная страница
Навигация по странице:

  • Генерирование исключений вручную 360

  • Повторное генерирование исключений

  • Исключения “под микроскопом”

  • Наиболее употребительные исключения

  • Исключение

  • Наследование классов исключений

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

  • Использование ключевых слов checked и unchecked

  • Справочник по C# Герберт Шилдт ббк 32. 973. 26018 75 Ш57 удк 681 07 Издательский дом "Вильямс" Зав редакцией


    Скачать 5.05 Mb.
    НазваниеСправочник по C# Герберт Шилдт ббк 32. 973. 26018 75 Ш57 удк 681 07 Издательский дом "Вильямс" Зав редакцией
    АнкорC #.pdf
    Дата08.12.2017
    Размер5.05 Mb.
    Формат файлаpdf
    Имя файлаC #.pdf
    ТипСправочник
    #10795
    страница23 из 52
    1   ...   19   20   21   22   23   24   25   26   ...   52
    358
    Часть I. Язык C#
    Перехват всех исключений
    Иногда требуется перехватывать все исключения, независимо от их типа. Для этого используйте catch
    -инструкцию без параметров. В этом случае создается обработчик
    “глобального перехвата”, который используется, чтобы программа гарантированно обработала все исключения. В следующей программе приведен пример использования такого обработчика, который успешно перехватывает генерируемые здесь исключение типа
    IndexOutOfRangeException и исключение типа
    DivideByZeroException
    // Использование catch-инструкции для
    // "глобального перехвата". using System; class ExcDemo5 { public static void Main() {
    //
    Здесь массив numer длиннее массива denom. int[] numer = { 4, 8, 16, 32, 64, 128, 256, 512 }; int[] denom = { 2, 0, 4, 4, 0, 8 }; for(int i=0; i < numer.Length; i++) { try
    {
    Console.WriteLine(numer[i] + " / " + denom[i]
    +
    " равно " + numer[i]/denom[i]);
    } catch
    {
    Console.WriteLine(
    "Произошло некоторое исключение.");
    }
    }
    }
    }
    Вот как выглядят результаты выполнения этой программы:
    4/2 равно 2
    Произошло некоторое исключение.
    16/4 равно 4 32/4 равно 8
    Произошло некоторое исключение.
    128 / 8 равно 16
    Произошло некоторое исключение.
    Произошло некоторое исключение.
    В отношении catch
    -инструкции, предназначенной для “глобального перехвата”, необходимо запомнить следующее: она должна быть последней в последовательности catch
    -инструкций.
    Вложение try-блоков
    Один try
    -блок можно вложить в другой. Исключение, сгенерированное во внутреннем try
    -блоке и не перехваченное catch
    -инструкцией, которая связана с этим try
    -блоком, передается во внешний try
    -блок. Например, в следующей программе исключение типа
    IndexOutOfRangeException перехватывается не внутренним try
    - блоком, а внешним.

    Глава 13. Обработка исключительных ситуаций
    359
    // Использование вложенного try-блока. using System; class NestTrys { public static void Main() {
    //
    Здесь массив numer длиннее массива denom. int[] numer = { 4, 8, 16, 32, 64, 128, 256, 512 }; int[] denom = { 2, 0, 4, 4, 0, 8 }; try {
    //
    Внешний try-блок. for(int i=0; i < numer.Length; i++) { try
    {
    // Вложенный try-блок.
    Console.WriteLine(numer[i] + " / " + denom[i]
    +
    " равно " + numer[i]/denom[i]);
    } catch(DivideByZeroException)
    {
    // Перехватываем исключение.
    Console.WriteLine("На нуль делить нельзя!");
    }
    }
    } catch(IndexOutOfRangeException)
    {
    //
    Перехватываем исключение.
    Console.WriteLine("Нет соответствующего элемента.");
    Console.WriteLine(
    "Неисправимая ошибка — программа завершена.");
    }
    }
    }
    Вот результаты выполнения этой программы:
    4/2 равно 2
    На нуль делить нельзя!
    16/4 равно 4 32/4 равно 8
    На нуль делить нельзя!
    128 / 8 равно 16
    Нет соответствующего элемента.
    Неисправимая ошибка — программа завершена.
    Исключение, которое может быть обработано внутренним try
    -блоком (в данном случае это деление на нуль), позволяет программе продолжать работу. Однако нарушение границ массива перехватывается внешним try
    -блоком и заставляет программу завершиться.
    В предыдущей программе хочется обратить ваше внимание вот на что. Чаще всего использование вложенных try
    -блоков обусловлено желанием обрабатывать различные категории ошибок различными способами. Одни типы ошибок носят катастрофический характер и не подлежат исправлению. Другие — неопасны для дальнейшего функционирования программы, и с ними можно справиться прямо на месте их возникновения. Многие программисты используют внешний try
    -блок для перехвата самых серьезных ошибок, позволяя внутренним try
    -блокам обрабатывать менее опасные.
    Внешние try
    -блоки можно также использовать в качестве механизма “глобального перехвата” для обработки тех ошибок, которые не перехватываются внутренним блоком.
    Генерирование исключений вручную

    360
    Часть I. Язык C#
    В предыдущих примерах демонстрировался перехват исключений, сгенерированных автоматически средствами C#. Однако можно сгенерировать исключение вручную, используя инструкцию throw
    . Формат ее записан таков: throw
    exceptOb
    ;
    Элемент
    exceptOb
    — это объект класса исключений, производного от класса
    Exception
    Рассмотрим пример, который демонстрирует использование инструкции throw для генерирования исключения типа
    DivideByZeroException вручную.
    // Генерирование исключения вручную. using System; class ThrowDemo { public static void Main() { try
    {
    Console.WriteLine("До генерирования исключения."); throw new DivideByZeroException();
    } catch(DivideByZeroException) {
    //
    Перехватываем исключение.
    Console.WriteLine("Исключение перехвачено.");
    }
    Console.WriteLine("После try/catch-блока.");
    }
    }
    Результаты выполнения этой программы имеют такой вид:
    До генерирования исключения.
    Исключение перехвачено.
    После try/catch-блока.
    Обратите внимание на то, как был создан объект исключения типа
    DivideByZeroException
    , а именно: с помощью оператора new в инструкции throw
    Помните, что инструкция throw генерирует объект, ведь нельзя просто сгенерировать “тип исключения”. В данном случае при создании объекта класса
    DivideByZeroException использовался конструктор по умолчанию, но для генерирования исключений предусмотрены и другие конструкторы.
    Чаще всего генерируемые исключения являются экземплярами классов исключений, создаваемых в программе. Как будет показано далее в этой главе, создание собственных классов исключений позволяет обрабатывать ошибки в коде, и эта процедура может стать частью общей стратегии обработки исключений программы.
    Повторное генерирование исключений
    Исключение, перехваченное одной catch
    -инструкцией, можно перегенерировать, чтобы обеспечить возможность его перехвата другой (внешней) catch
    -инструкцией. Самая распространенная причина для повторного генерирования исключения — позволить нескольким обработчикам получить доступ к исключению. Например, возможна такая ситуация, что один обработчик исключений управляет одним аспектом исключения, а второй — другим. Чтобы повторно сгенерировать исключение, достаточно

    Глава 13. Обработка исключительных ситуаций
    361 использовать ключевое слово throw
    , не указывая исключения. Другими словами, используйте следующую форму инструкции throw throw;
    Помните, что при повторном генерировании исключения оно не будет повторно перехватываться той же catch
    -инструкцией, а передается следующей catch
    -инструкции.
    Повторное генерирование исключений демонстрируется в следующей программе. В данном случае она перегенерирует исключение типа
    IndexOutOfRangeException
    // Повторное генерирование исключения. using System; class Rethrow { public static void genException() {
    //
    Здесь массив numer длиннее массива denom. int[] numer = { 4, 8, 16, 32, 64, 128, 256, 512 }; int[] denom = { 2, 0, 4, 4, 0, 8 }; for(int i=0; i{
    Console.WriteLine(numer[i] + " / " + denom[i]
    +
    " равно " + numer[i]/denom[i]);
    } catch(DivideByZeroException)
    {
    // Перехватываем исключение.
    Console.WriteLine("Делить на нуль нельзя!");
    } catch(IndexOutOfRangeException)
    {
    // Перехватываем исключение.
    Console.WriteLine(
    "Нет соответствующего элемента."); throw;
    //
    Генерируем исключение повторно.
    }
    }
    }
    } class RethrowDemo { public static void Main() { try
    {
    Rethrow.genException();
    } catch(IndexOutOfRangeException) {
    //
    Перехватываем повторно
    // сгенерированное исключение.
    Console.WriteLine("Неисправимая ошибка — " +
    "программа завершена.");
    }
    }
    }
    В этой программе ошибки деления на нуль обрабатываются локально (по месту), т.е. в самом методе genException()
    , но ошибка нарушения границ массива генерируется повторно. В данном случае исключение типа
    IndexOutOfRangeException обрабатывается функцией
    Main()

    362
    Часть I. Язык C#
    Использование блока finally
    Иногда возникает потребность определить программный блок, который должен выполняться по выходу из try/catch
    -блока. Например, исключение может вызвать ошибку, которая завершает текущий метод и, следовательно, является причиной преждевременного возврата. Однако такой метод может оставить открытым файл или соединение с сетью, которые необходимо закрыть. Подобные обстоятельства — обычное явление в программировании, и C# предоставляет удобный путь выхода из них: блок finally
    Чтобы определить блок кода, подлежащий выполнению по выходу из try/catch
    - блока, включите в конец try/catch
    -последовательности блок finally
    . Общая форма записи последовательности try/catch
    -блоков, содержащей блок finally
    , выглядит следующим образом. try {
    //
    Блок кода, предназначенный для обработки ошибок.
    } catch(
    ExcepType1
    exOb
    ) {
    //
    Обработчик для исключения типа ExcepType1,
    } catch(
    ExcepType2
    exOb
    ) {
    //
    Обработчик для исключения типа ExcepType2.
    } finally {
    //
    Код завершения обработки исключений.
    }
    Блок finally будет выполнен после выхода из try/catch
    -блока, независимо от условий его выполнения. Другими словами, при нормальном завершении try
    -блока или в условиях возникновения исключения содержимое finally
    -блока будет безусловно отработано. Блок finally выполнится и в том случае, если любой код внутри try
    -блока или любая из его catch-инструкций определены внутри метода. Вот пример использования блока finally
    :
    // Использование блока finally. using System; class UseFinally { public static void genException(int what) { int t; int[] nums = new int[2];
    Console.WriteLine("Получаем " + what); try
    { switch(what)
    { case
    0: t = 10 / what; // Генерируем ошибку
    // деления на нуль. break; case
    1: nums[4]
    =
    4;
    //
    Генерируем ошибку
    // индексирования массива.

    Глава 13. Обработка исключительных ситуаций
    363 break; case
    2: return;
    // возврат из try-блока.
    }
    } catch(DivideByZeroException) {
    //
    Перехватываем исключение.
    Console.WriteLine("На нуль делить нельзя!"); return;
    //
    Возврат из catch-блока.
    } catch(IndexOutOfRangeException) {
    //
    Перехватываем исключение.
    Console.WriteLine("Нет соответствующего элемента.");
    } finally
    {
    Console.WriteLine("По окончании try-блока.");
    }
    }
    } class FinallyDemo { public static void Main() { for(int i=0; i < 3; i++) {
    UseFinally.genException(i);
    Console.WriteLine();
    }
    }
    }
    Вот какие результаты получены при выполнении этой программы:
    Получаем 0
    На нуль делить нельзя!
    По окончании try-блока.
    Получаем 1
    Нет соответствующего элемента.
    По окончании try-блока.
    Получаем 2
    По окончании try-блока.
    Как подтверждают результаты выполнения этой программы, независимо от итога завершения try
    -блока, блок finally выполняется обязательно.
    Исключения “под микроскопом”
    До сих пор мы перехватывали исключения, но ничего не делали с самим объектом исключения. Как упоминалось выше, catch
    -инструкция позволяет задать тип исключения и параметр. Параметр предназначен для принятия объекта исключения. Поскольку все классы исключений являются производными от класса
    Exception
    , все исключения поддерживают члены, определенные в этом классе. В этой главе мы рассмотрим наиболее полезные члены и конструкторы класса
    Exception и узнаем преимущества использования параметра catch
    -инструкции.
    В классе
    Exception определен ряд свойств. Самые интересные из них:
    Message
    ,
    StackTrace и
    TargetSite
    . Все они предназначены только для чтения. Свойство

    364
    Часть I. Язык C#
    Message содержит строку, которая описывает причину ошибки, а свойство
    StackTrace
    — строку со стеком вызовов, приведших к возникновению исключений. Свойство
    TargetSite принимает объект, который задает метод, сгенерировавший исключение.
    В классе Exception также определен ряд методов. Чаще всего используется метод
    ToString()
    , который возвращает строку с описанием исключения. Метод
    ToString()
    автоматически вызывается, если некоторое исключение отображается, например, с помощью метода
    WriteLine()
    . Использование свойств и методов класса
    Exception демонстрируется в следующей программе.
    // Использование членов класса Exception. using System; class ExcTest { public static void genException() { int[] nums = new int[4];
    Console.WriteLine("Перед генерированием исключения.");
    //
    Генерируем исключение, связанное с попаданием
    // индекса вне диапазона. for(int i=0; i < 10; i++) { nums[i]
    = i;
    Console.WriteLine("nums[{0}]: {1}", i, nums[i]);
    }
    Console.WriteLine("Этот текст не отображается.");
    }
    } class UseExcept { public static void Main() { try
    {
    ExcTest.genException();
    } catch(IndexOutOfRangeException exc) {
    //
    Перехватываем исключение.
    Console.WriteLine("Стандартное сообщение таково: ");
    Console.WriteLine(exc);
    //
    Вызов метода ToString().
    Console.WriteLine("Свойство StackTrace: " + exc.StackTrace);
    Console.WriteLine("Свойство Message: " + exc.Message);
    Console.WriteLine("Свойство TargetSite: " + exc.TargetSite);
    }
    Console.WriteLine("После catch-инструкции.");
    }
    }
    Вот результаты выполнения этой программы:
    Перед генерированием исключения. nums[0]: 0 nums[1]: 1 nums[2]: 2 nums[3]: 3

    Глава 13. Обработка исключительных ситуаций
    365
    Стандартное сообщение таково:
    System.IndexOutOfRangeException: Index was outside the bounds of the array. at
    ExcTest.genException() at UseExcept.Main()
    Свойство StackTrace: at ExcTest.genException() at UseExcept.Main()
    Свойство Message: Index was outside the bounds of the array.
    Свойство TargetSite: Void genException()
    После catch-инструкции.
    В классе
    Exception определено четыре конструктора. Наиболее часто используются такие:
    Exception()
    Exception(string str)
    Первый — это конструктор по умолчанию. Второй принимает значение свойства
    Message
    , связанное с исключением. При создании собственных классов исключений необходимо реализовать оба этих конструктора.
    Наиболее употребительные исключения
    В пространстве имен
    System определено несколько стандартных встроенных исключений. Все они выведены из класса
    SystemException
    , поскольку генерируются системой динамического управления (Common Language Runtime) при возникновении динамических ошибок. Некоторые из самых употребительных стандартных исключений, определенных в C#, приведены в табл. 13.1.
    Таблица 13.1. Наиболее употребительные исключения, определенные в пространстве имен
    System
    Исключение
    Значение
    ArrayTypeMismatchException
    Тип сохраняемого значения несовместим с типом массива
    DivideByZeroException
    Попытка деления на нуль
    IndexOutOfRangeException
    Индекс массива оказался вне диапазона
    InvalidCastException
    Неверно выполнено динамическое приведение типов
    OutOfMemoryException
    Обращение к оператору new оказалось неудачным из-за недостаточного объема свободной памяти
    OverflowException
    Имеет место арифметическое переполнение
    NullReferenceException
    Была сделана попытка использовать нулевую ссылку, т.е. ссылку, которая не указывает ни на какой объект
    StackOverflowException
    Переполнение стека
    Большинство исключений, перечисленных в табл. 13.1, не нуждается в дополнительных разъяснениях, за исключением класса
    NullReferenceException
    . Это исключение генерируется при попытке использовать нулевую ссылку, например, при попытке вызвать метод, передав ему вместо ссылки на объект нулевую ссылку. Нулевая
    ссылка не указывает ни на какой объект. Один из способов создать нулевую ссылку — явно присвоить ссылочной переменной null
    -значение, используя ключевое слово null
    Нулевые ссылки можно получить и другими путями, которые, однако, менее очевидны.
    Рассмотрим программу, в которой демонстрируется возникновение исключения типа
    NullReferenceException

    366
    Часть I. Язык C#
    // Использование исключения типа NullReferenceException. using System; class X { int x; public X(int a) { x = a;
    } public int add(X o) { return x + o.x;
    }
    }
    // Демонстрируем исключение типа NullReferenceException. class NREDemo { public static void Main() {
    X p = new X(10);
    X q = null; // Переменной q явно присваивается
    // значение null. int val; try
    { val = p.add(q); // Такой вызов метода
    // приведет к исключению.
    } catch(NullReferenceException)
    {
    Console.WriteLine("NullReferenceException!");
    Console.WriteLine("Исправляем ошибку...\n");
    //
    Исправляем ошибку. q = new X(9); val = p.add(q);
    }
    Console.WriteLine("Значение val равно {0}", val);
    }
    }
    При выполнении этой программы получаем такие результаты:
    NullReferenceException!
    Исправляем ошибку...
    Значение val равно 19
    Эта программа создает класс
    X
    , в котором определяется член x
    и метод add()
    , предназначенный для сложения значения x
    , принадлежащего вызывающему объекту, с членом x
    , который определен в объекте, переданном в качестве параметра. В методе
    Main() создаются два объекта класса x
    . Первый, p
    , инициализируется, а второй, q
    , — нет
    (ему явным образом присваивается значение null
    ). Затем вызывается метод p.meth()
    , которому значение q
    передается как аргумент. Поскольку переменная q
    не ссылается ни на один объект, при попытке получить значение члена q.x генерируется исключение типа
    NullReferenceException
    Заслуживает внимания исключение типа
    StackOverflowException
    , которое генерируется при переполнении системного стека. Оно может возникнуть при некорректном определении рекурсивного метода. Программисту, который увлекается рекурсией,

    Глава 13. Обработка исключительных ситуаций
    367 возможно, стоит внимательно отследить появление исключения этого типа, приняв соответствующие меры в случае его обнаружения. Однако здесь следует проявить осторожность. Если уж это исключение сгенерировано, значит, системный стек исчерпал свои возможности, поэтому лучше всего просто начать анализ с рекурсивного вызова.
    Наследование классов исключений
    Несмотря на то что встроенные C#-исключения обрабатывают самые распространенные ошибки, C#-механизм обработки исключений не ограничивается этими ошибками. В C# имеется возможность обрабатывать исключения, создаваемые программистом. В своих программах вы можете использовать для обработки ошибок
    “собственные” исключения. В создании исключения нет ничего сложного. Для этого достаточно определить класс как производный от класса
    Exception
    . Как правило, определяемые программистом исключения, должны быть производными от класса
    ApplicationException
    , “родоначальника” иерархии, зарезервированной для исключений, связанных с прикладными программами. Созданные вами производные классы не должны ничего реализовывать, поскольку одно лишь их существование в системе типов уже позволит использовать их в качестве исключений.
    Классы исключений, создаваемые программистом, будут автоматически иметь свойства и методы, определенные в классе
    Exception и доступные для них. Конечно, один или несколько членов в новых классах можно переопределить.
    Рассмотрим пример, в котором создается “пользовательский” тип исключения. В конце главы 10 был приведен пример разработки класса массива с именем
    RangeArray
    Вспомним, что класс
    RangeArray поддерживает одномерные int
    -массивы, в которых начальный и конечный индексы задаются пользователем. Например, массив, индексы которого лежат в диапазоне от -5 до 27, абсолютно легален для объектов класса
    RangeArray
    . В главе 10 было показано, что при попадании индекса за пределы диапазона, устанавливалась переменная ошибки, определенная в классе
    RangeArray
    . Это означает, что переменную ошибки необходимо было проверять после каждой операции, в которой участвовал объект класса
    RangeArray
    . Безусловно, такое решение связано с ошибками и лишено “изящества”. Предпочтительней, чтобы объект класса
    RangeArray при возникновении ошибки нарушения границ диапазона генерировал “свое” исключение.
    Именно такое решение и реализовано в следующей версии класса
    RangeArray
    // Создание пользовательского исключения для
    // обнаружения ошибок при работе с объектами класса
    // RangeArray. using System;
    // Создаем исключение для класса RangeArray. class RangeArrayException : ApplicationException {
    //
    Реализуем стандартные конструкторы. public RangeArrayException() : base() { } public RangeArrayException(string str) : base(str) { }
    //
    Переопределяем метод ToString() для класса
    // RangeArrayException. public override string ToString() { return
    Message;
    }
    }

    368
    Часть I. Язык C#
    // Улучшенная версия класса RangeArray. class RangeArray {
    //
    Закрытые данные. int[] a; // Ссылка на базовый массив. int lowerBound; // Наименьший индекс. int upperBound; // Наибольший индекс. int len; // Базовая переменная для свойства Length.
    //
    Создаем массив с заданным размером. public RangeArray(int low, int high) { high++; if(high <= low) { throw new
    RangeArrayException(
    "Нижний индекс не меньше верхнего.");
    } a = new int[high - low]; len = high - low; lowerBound = low; upperBound = --high;
    }
    //
    Свойство Length, предназначенное только для чтения. public int Length { get { return len;
    }
    }
    //
    Индексатор для объекта класса RangeArray. public int this[int index] {
    //
    Средство для чтения элемента массива. get
    { if(ok(index))
    { return a[index - lowerBound];
    } else { throw new
    RangeArrayException(
    "Ошибка нарушения границ диапазона.");
    }
    }
    //
    Средство для записи элемента массива. set
    { if(ok(index))
    { a[index - lowerBound] = value;
    } else throw new RangeArrayException(
    "Ошибка нарушения границ диапазона.");
    }
    }
    //
    Метод возвращает значение true,
    // если индекс в пределах диапазона. private bool ok(int index) { if(index >= lowerBound & index <= upperBound) return true; return false;
    }
    }

    Глава 13. Обработка исключительных ситуаций
    369
    // Демонстрируем использование массива с заданным
    // диапазоном изменения индекса. class RangeArrayDemo { public static void Main() { try
    {
    RangeArray ra = new RangeArray(-5, 5);
    RangeArray ra2 = new RangeArray(1, 10);
    //
    Демонстрируем использование объекта-массива ra.
    Console.WriteLine("Длина массива ra: " + ra.Length); for(int i = -5; i <= 5; i++) ra[i]
    = i;
    Console.Write("Содержимое массива ra: "); for(int i = -5; i <= 5; i++)
    Console.Write(ra[i]
    +
    "
    ");
    Console.WriteLine("\n");
    //
    Демонстрируем использование объекта-массива ra2.
    Console.WriteLine("Длина массива ra2: " + ra2.Length); for(int i = 1; i <= 10; i++) ra2[i]
    = i;
    Console.Write("Содержимое массива ra2 : "); for(int i = 1; i <= 10; i++)
    Console.Write(ra2[i]
    +
    "
    ");
    Console.WriteLine("\n");
    } catch(RangeArrayException exc) {
    Console.WriteLine(exc);
    }
    //
    Теперь демонстрируем "работу над ошибками".
    Console.WriteLine(
    "Сгенерируем ошибки непопадания в диапазон.");
    //
    Используем неверно заданный конструктор. try
    {
    RangeArray ra3 = new RangeArray(100, -10); // Ошибка!
    } catch(RangeArrayException exc) {
    Console.WriteLine(exc);
    }
    //
    Используем неверно заданный индекс. try
    {
    RangeArray ra3 = new RangeArray(-2, 2); for(int i = -2; i <= 2; i++) ra3[i]
    = i;
    Console.Write("Содержимое массива ra3: "); for(int i = -2; i <= 10; i++)
    // Ошибка непопадания
    // в диапазон.
    Console.Write(ra3[i]
    +
    "
    ");

    370
    Часть I. Язык C#
    } catch(RangeArrayException exc) {
    Console.WriteLine(exc);
    }
    }
    }
    При выполнении этой программы получаем такие результаты:
    Длина массива ra: 11
    Содержимое массива ra: -5 -4 -3 -2 -1 0 1 2 3 4 5
    Длина массива ra2: 10
    Содержимое массива ra2: 1 2 3 4 5 6 7 8 9 10
    Сгенерируем ошибки непопадания в диапазон.
    Нижний индекс не меньше верхнего.
    Содержимое массива ra3: -2 -1 0 1 2 Ошибка нарушения границ диапазона.
    При возникновении ошибки нарушения границ диапазона
    RangeArray
    -объект генерирует объект типа
    RangeArrayException
    . Этот класс — производный от класса
    ApplicationException
    . Как упоминалось выше, класс исключений, создаваемый программистом, обычно выводится из класса
    ApplicationException
    . Обратите внимание на то, что подобная ошибка может обнаружиться во время создания
    RangeArray
    -объекта. Чтобы перехватывать такие исключения, объекты класса
    RangeArray должны создаваться внутри блока try
    , как это показано в программе. С использованием исключений для сообщений об ошибках класс
    RangeArray теперь напоминает один из встроенных C#-типов и может быть полностью интегрирован в механизм обработки исключений любой программы.
    Прежде чем переходить к следующему разделу, “поиграйте” с этой программой.
    Например, попробуйте закомментировать переопределение метода
    ToString()
    и посмотрите результат. Попробуйте также создать исключение, используя конструктор по умолчанию, и посмотрите, какое сообщение в этом случае сгенерирует C#.
    Перехват исключений производных классов
    При перехвате исключений, типы которых включают базовые и производные классы, необходимо обращать внимание на порядок catch
    -инструкций, поскольку catch
    - инструкция для базового класса соответствует любой catch
    -инструкции производных классов. Например, поскольку базовым классом для всех исключений является
    Exception
    , при перехвате исключения типа
    Exception будут перехватываться все возможные исключения. Конечно, как упоминалось выше, использование catch
    - инструкции без аргумента обеспечивает более ясный способ перехвата всех исключений.
    Однако проблема перехвата исключений производных классов очень важна в других контекстах, особенно в случае создания собственных классов исключений.
    Если нужно перехватывать исключения и базового, и производного класса, поместите первой в catch
    -последовательности инструкцию с заданием производного класса. В противном случае catch
    -инструкция с заданием базового класса будет перехватывать все исключения производных классов. Это правило — вынужденная мера, поскольку размещение catch
    -инструкции для базового класса перед остальными catch
    - инструкциями сделает их недостижимыми, и они никогда не будут выполнены. В C# недостижимая catch
    -инструкция считается ошибкой.
    Следующая программа создает два класса исключений с именами
    ExceptA
    и
    ExceptB
    . Класс
    ExceptA
    выводится из класса
    ApplicationException
    , а класс
    ExceptB
    — из класса
    ExceptA
    . Затем программа генерирует исключение каждого типа.

    Глава 13. Обработка исключительных ситуаций
    371
    // Инструкции перехвата исключений производных классов
    // должны стоять перед инструкциями перехвата исключений
    // базовых классов. using System;
    // Создаем класс исключения. class ExceptA : ApplicationException { public ExceptA() : base() { } public ExceptA(string str) : base(str) { } public override string ToString() { return Message;
    }
    }
    // Создаем класс исключения как производный
    //от класса ExceptA. class ExceptB : ExceptA { public ExceptB() : base() { } public ExceptB(string str) : base(str) { } public override string ToString() { return Message;
    }
    } class OrderMatters { public static void Main() { for(int x = 0; x < 3; x++) { try
    { if(x==0) throw new
    ExceptA(
    "Перехват исключения типа ExceptA."); else if(x==1) throw new ExceptB(
    "Перехват исключения типа ExceptB."); else throw new Exception();
    } catch(ExceptB exc)
    { //
    Перехватываем исключение.
    Console.WriteLine(exc);
    } catch(ExceptA exc)
    { //
    Перехватываем исключение.
    Console.WriteLine(exc);
    } catch(Exception exc)
    {
    Console.WriteLine(exc);
    }
    }
    }
    }
    Вот результаты выполнения этой программы:
    Перехват исключения типа ExceptA.
    Перехват исключения типа ExceptB.
    System.Exception: Exception of type System.Exception was thrown. at
    OrderMatters.Main()
    Обратите внимание на порядок следования catch
    -инструкций. Это — единственно правильный вариант. Поскольку класс
    ExceptB
    выведен из класса
    ExceptA
    , catch
    - инструкция для исключений типа
    ExceptB
    должна стоять перед инструкцией,

    372
    Часть I. Язык C# предназначенной для перехвата исключений типа
    ExceptA
    . Точно так же catch
    - инструкция для исключений класса
    Exception
    (который является базовым для всех исключений) должна стоять последней. Чтобы убедиться в этом, попробуйте переставить catch
    -инструкции в другом порядке. Это приведет к ошибке при компиляции.
    Часто catch
    -инструкцию, определенную для исключений базового класса, успешно используют для перехвата целой категории исключений. Предположим, вы создаете набор исключений для некоторого устройства. Если вывести все эти исключения из общего базового класса, то приложения, которым не требуется подробные сведения о возникшей проблеме, могли бы просто перехватывать исключение, настроенное на базовый класс, избегнув тем самым ненужного дублирования кода.
    Использование ключевых слов checked
    и unchecked
    В C# предусмотрено специальное средство, которое связано с генерированием исключений, связанных с переполнением в арифметических вычислениях. Как вы знаете, в некоторых случаях при вычислении арифметических выражений получается результат, который выходит за пределы диапазона, определенного для типа данных в выражении. В этом случае говорят, что произошло переполнение результата. Рассмотрим, например, такой фрагмент программы: byte a, b, result; a = 127; b = 127; result = (byte)(a * b);
    Здесь произведение значений a
    и b
    превышает диапазон представления значений типа byte
    . Следовательно, результат вычисления этого выражения вызвал переполнение для типа переменной result
    C# позволяет управлять генерированием исключений при возникновении переполнения с помощью ключевых слов checked и unchecked
    . Чтобы указать, что некоторое выражение должно быть проконтролировано на предмет переполнения, используйте ключевое слово checked
    . А чтобы проигнорировать переполнение, используйте ключевое слово unchecked
    . В последнем случае результат будет усечен так, чтобы его тип соответствовал типу-результату выражения.
    Ключевое слово checked имеет две формы. Одна проверяет конкретное выражение и называется операторной checked
    -формой. Другая же проверяет блок инструкций. checked(
    expr
    ) checked {
    //
    Инструкции, подлежащие проверке.
    }
    Здесь
    expr
    — выражение, которое необходимо контролировать. Если значение контролируемого выражения переполнилось, генерируется исключение типа
    OverflowException
    Ключевое слово unchecked имеет две формы. Одна из них — операторная форма, которая позволяет игнорировать переполнение для заданного выражения. Вторая игнорирует переполнение, которое возможно в блоке инструкций. unchecked(
    expr
    )

    Глава 13. Обработка исключительных ситуаций
    373 unchecked {
    //
    Инструкции, для которых переполнение игнорируется.
    }
    Здесь
    expr
    — выражение, которое не проверяется на предмет переполнения. В случае переполнения это выражение усекается.
    Рассмотрим программу, которая демонстрирует использование как слова checked
    , так и слова unchecked
    // Использование ключевых слов checked и unchecked. using System; class CheckedDemo { public static void Main() { byte a, b; byte result; a = 127; b = 127; try
    { result = unchecked((byte) (a * b) );
    Console.WriteLine("Unchecked-результат: " + result); result = checked((byte)(a * b)); // Эта инструкция
    // вызывает исключение.
    Console.WriteLine("Checked-реэультат: " + result);
    //
    Инструкция не будет
    // выполнена.
    } catch(OverflowException exc) {
    //
    Перехватываем исключение.
    Console.WriteLine(exc);
    }
    }
    }
    При выполнении этой программы получаются такие результаты:
    Unchecked-результат: 1
    System.OverflowException: Arithmetic operation resulted in an overflow. at CheckedDemo.Main()
    Как видите, результат непроверяемого выражения усекается. В случае разрешения проверки переполнения было бы сгенерировано исключение.
    В предыдущей программе было продемонстрировано использование ключевых слов checked и unchecked для одного выражения. На примере следующей программы показано, как можно избежать переполнения при выполнении блока инструкций.
    // Использование ключевых слов checked и unchecked
    // для блоков инструкций. using System; class CheckedBlocks { public static void Main() { byte a, b; byte result;

    374
    Часть I. Язык C# a = 127; b = 127; try
    { unchecked
    { a
    =
    127; b
    =
    127; result = unchecked((byte)(a * b));
    Console.WriteLine("Unchecked-результат: " + result); a
    =
    125; b
    =
    5; result = unchecked((byte)(a * b));
    Console.WriteLine("Unchecked-реэультат: " + result);
    } checked
    { a
    =
    2; b
    =
    7; result = checked((byte)(a * b)); // Все в порядке.
    Console.WriteLine("Checked-результат: " + result); a
    =
    127; b
    =
    127; result = checked((byte)(a * b)); // Здесь должно
    // быть сгенерировано
    // исключение.
    Console.WriteLine("Checked-результат: " + result);
    //
    Эта инструкция не
    // выполнится.
    }
    } catch(OverflowException exc) {
    //
    Перехватываем исключение.
    Console.WriteLine(exc);
    }
    }
    }
    Вот как выглядят результаты выполнения этой программы:
    Unchecked-результат: 1
    Unchecked-результат: 113
    Checked-результат: 14
    System.OverflowException: Arithmetic operation resulted in an overflow. at CheckedBlocks.Main()
    Как видите, при выполнении unchecked
    -блока результат вычисления выражения при наличии переполнения усекается. При возникновении переполнения в checked
    -блоке генерируется исключение.
    Управление генерированием исключений с помощью ключевых слов checked или unchecked может быть полезным в том случае, когда checked/unchecked
    -статус переполнения определяется использованием соответствующей опции компилятора и настройкой самой среды выполнения. Таким образом, для некоторых типов программ лучше всего явно задавать статус контроля переполнения.

    Полный справочник по
    1   ...   19   20   21   22   23   24   25   26   ...   52


    написать администратору сайта