1629295407-c61bfe4caba98380ea3e7cdae6295416 (846200), страница 66
Текст из файла (страница 66)
В данном случае при создании объекта класса DivideByZeroExceptionиспользовался конструктор по умолчанию, но для генерирования исключенийпредусмотрены и другие конструкторы.Чаще всего генерируемые исключения являются экземплярами классов исключений,создаваемых в программе. Как будет показано далее в этой главе, создание собственныхклассов исключений позволяет обрабатывать ошибки в коде, и эта процедура может статьчастью общей стратегии обработки исключений программы.Повторное генерирование исключенийИсключение, перехваченное одной catch-инструкцией, можно перегенерировать,чтобы обеспечить возможность его перехвата другой (внешней) catch-инструкцией.
Самаяраспространенная причина для повторного генерирования исключения — позволитьнескольким обработчикам получить доступ к исключению. Например, возможна такаяситуация, что один обработчик исключений управляет одним аспектом исключения, авторой — другим. Чтобы повторно сгенерировать исключение, достаточно360Часть I. Язык C#использовать ключевое слово 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<numer.Length; i++) {try {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().Глава 13. Обработка исключительных ситуаций361Использование блока 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;// Генерируем ошибку// индексирования массива.362Часть I.
Язык C#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.
Все они предназначены только для чтения. СвойствоГлава 13. Обработка исключительных ситуаций363Message содержит строку, которая описывает причину ошибки, а свойство 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]: 0nums[1]: 1nums[2]: 2nums[3]: 3364Часть I.
Язык C#Стандартное сообщение таково:System.IndexOutOfRangeException: Index was outsidethe 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ИсключениеArrayTypeMismatchExceptionDivideByZeroExceptionIndexOutOfRangeExceptionInvalidCastExceptionOutOfMemoryExceptionOverflowExceptionNullReferenceExceptionStackOverflowExceptionЗначениеТип сохраняемого значения несовместим с типом массиваПопытка деления на нульИндекс массива оказался вне диапазонаНеверно выполнено динамическое приведение типовОбращение к оператору new оказалось неудачным из-занедостаточного объема свободной памятиИмеет место арифметическое переполнениеБыла сделана попытка использовать нулевую ссылку, т.е.ссылку, которая не указывает ни на какой объектПереполнение стекаБольшинство исключений, перечисленных в табл.
13.1, не нуждается вдополнительных разъяснениях, за исключением класса NullReferenceException. Этоисключение генерируется при попытке использовать нулевую ссылку, например, припопытке вызвать метод, передав ему вместо ссылки на объект нулевую ссылку. Нулеваяссылка не указывает ни на какой объект. Один из способов создать нулевую ссылку — явноприсвоить ссылочной переменной null-значение, используя ключевое слово null.Нулевые ссылки можно получить и другими путями, которые, однако, менее очевидны.Рассмотрим программу, в которой демонстрируется возникновение исключения типаNullReferenceException.Глава 13.
Обработка исключительных ситуаций365// Использование исключения типа 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, котороегенерируется при переполнении системного стека. Оно может возникнуть принекорректном определении рекурсивного метода.