1629295403-b876e2087bddebea4bc9666fb2377a02 (846199), страница 79
Текст из файла (страница 79)
Дополнительные главнpublic class Program{public static void Main(string[]args){try // Исключения от функции Factorial()// этого блока"всплывут" до{// Вызов функции вычисления факториала в// цикле от 6 до -6for (int i = 6; i > -6; i--){// Вычисление факториалаdouble dFactorial = MyMathFunctions.Factorial(i);// Вывод результата на каждой итерацииConsole.WriteLine("i = { о } , факториал = {l}",i, MyMathFunctions.Factorial(i));catch(Exception e) // ...
перехват исключения{Console.WriteLine("Ошибка:");Console.WriteLine(e.ToString());}// Ожидаем подтверждения пользователяConsole.WriteLine("Нажмите <Enter> для " +"завершения программы...");Console.Read();Эта "исключительная" версия функции Main () практически полностью находитсяв try-блоке.Всегда помещайте содержимое функции Main () в try-блок, поскольку функция Main () — начальная и конечная точка программы. Любое исключение, неперехваченное где-то в другом месте, будет передано функции Main ( ) . Этопоследняя возможность перехватить исключение перед тем, как оно попадетпрямо в Windows, где это сообщение об ошибке будет гораздо сложнее интерпретировать.Блок catch в конце функции Main () перехватывает объект Exception и использует его метод ToString () для вывода информации об ошибке, содержащейся в этомобъекте в виде строки.Более консервативное свойство Exception.Message возвращает более удобочитаемую, но менее информативную информацию по сравнению с предоставляемой методом е .
ToString ().Эта версия функции Factorial () включает ту же проверку на отрицательность переданного аргумента, что и предыдущая (для экономии места в ней опущена проверкатого, что аргумент — целое число). Если аргумент отрицателен, функция Factorial ()403форматирует сообщение об ошибке с описанием ситуации, включая само отрицательноезначение, вызвавшее ошибку. Затем функция Factorial () вносит информацию ввновь создаваемый объект типа Exception, который передается с помощью механизмаисключений вызывающей функции.Вывод этой программы выглядит следующим образом:i = 6, факториалi = 5, факториалi = 4, факториалi = 3, факториалi = 2, факториалi = 1, факториалi = 0, факториалОшибка:=======720120246210System.Exception: Отрицательный аргумент в вызове Factorial -1at Factorial(Int32 nValue) inс:\c#programs\Factorial\Program.cs:line 21at FactorialException.Program.Main(String[] args) inс:\c#programs\Factorial\Program.cs:line 49Нажмите <Enter> для завершения программы...В первых нескольких строках выводятся корректно вычисленные факториалы число9от 6 до 0 .
Попытка вычислить факториал -1 приводит к генерации исключения.В первой строке сообщения об ошибке выводится информация, сгенерированнаяв функции Factorial ( ) . Эта строка описывает природу ошибки, включая вызвавшеенеприятности значение аргумента1.В оставшейся части вывода выполняется трассировка стека.
В первой строке указывается, в какой функции сгенерировано исключение. В данном случае это было сделанов функции Factorial (int) — а именно в 21 строке исходного файла Program.csФункция Factorial () была вызвана из функции Main (string [] ) в строке 49 тогоже файла. На этом трассировка файла прекращается, поскольку функция Main () содержит блок, перехвативший и обработавший указанное исключение.Трассировка стека доступна в одном из окон отладчика Visual Studio.Вы должны согласиться, что это весьма впечатляюще.
Сообщение об ошибке описывает случившееся и позволяет указать аргумент, приведший к ней. Трассировка стекаполностью отслеживает, где именно и в результате какой последовательности вызововпроизошла ошибка. При такой диагностике поиск ошибки и ее причины не должен составить никакого труда.Получение подробной информации, такой как трассировка стека, удобно в процессе разработки и отладки, но при выходе программы к конечным пользователям вам наверняка захочется, чтобы она выдавала более простые и понятные сообщения. (При этом все равно останется возможность записи трассировки стекав журнальный файл, чтобы вы могли владеть всей необходимой информацией,когда пользователь обратится к вам за помощью.)9404Еще раз напомним читателю, что в математике принято считать, что 0! = 1.
— Примеч. ред.Часть VII. Дополнительные главыСтандартный класс исключения Exception, предоставляемый библиотекой С#,в состоянии предоставить вам достаточное количество информации. Вы можете запросить объект исключения о том, где он был сгенерирован, какая строка была передана емугенерирующей функцией. Однако в ряде случаев стандартного класса Exception бывает недостаточно. У вас может оказаться слишком много информации, чтобы разместитьее в одной строке. Например, функция приложения может захотеть передать вызвавшийпроблемы объект для последующего анализа. Изучение этого объекта может быть полезным вплоть до полного восстановления после происшедшей ошибки.Локально определенный класс может наследовать класс Exception так же, каки любой другой класс.
Однако пользовательский класс исключения должен наследоватьне непосредственно класс Exception, а класс ApplicationException, являющийсяподклассом Exception, как показано в следующем фрагменте исходного текста:// CustomException - добавление ссылки на MyClass к// стандартному классу исключенияpublic class CustomException : ApplicationException{private MyClass myobject;// Хранит ссылку на вызвавший// проблемы объектCustomException(string sMsg, MyClass mo) : base(sMsg){myobject = m o ;' }// Предоставляет доступ к объекту,// исключенияpublic MyClass MyCustomObject{ getсохраненному в объекте{return myobject;}}}Класс CustomException представляет собой самодельный класс для сообщения обошибке в любой программе, работающей с классом MyClass.
Этот подкласс класса ApplicationException содержит такую же строку, как и исходный класс, но добавляетк ней ссылку на объект MyClass, вызвавший проблемы. Это позволяет произвести детальное исследование случившегося.В приведенном далее примере выполняется перехват исключения CustomException и используется информация об объекте MyClass:public class Program{public void SomeFunction(){try{// ...
действия перед вызовом демонстрационной функцииSomeOtherFunctionO ;// ... продолжение работы ...}catch(MyException me)[лава{ 18. ЭТИ исключительные исключения405// Здесь у вас имеется доступ к методам Exception и// ApplicationExceptionstring s = m e . T o S t r i n g О ;// Но у вас есть еще и доступ к методам, уникальным// для вашего класса исключенияMyClass mo = me.MyCustomObject;// Например, вы можете запросить у объекта MyClass его// собственное описаниеstring s = mo.GetDescription();}}publicvoidSomeOtherFunctionO{// Создание myobjectMyClass myobject = new M y C l a s s 0 ;// ...
сообщение об ошибке с участием myobject ..throw new MyException("Ошибка в объекте MyClass",myobj ect) ;// ... Остальная часть функции ...}}В этом фрагменте кода функция SomeFunction () вызывает функцию SomeO-|therFunction () из охватывающего блока try.
Функция SomeOtherFunction()создает и использует объект myobject. Где-то в функции SomeOtherFunction()программа проверки ошибок подготавливает исключение к генерации для сообщенияо происшедшей ошибке. Вместо создания простого объекта типа Exception или ApplicationException, функция SomeFunction О применяет разработанный ваштип MyExcept ion, пригодный не только для передачи текстового сообщения об ошибке, но и ссылки на вызвавший ее объект.Блок catch в функции Main () указывает, что он предназначен для перехвата объектов MyExcept ion. После того как такой объект перехвачен, код приложения в состоянии применить все методы Exception, как, например, метод ToString ( ) . Однако в этом catch-блоке может использоваться и другая информация, к примеру, вызовметодов объекта MyClass, ссылка на который передана в объекте исключения.Фрагмент кода в предыдущем разделе продемонстрировал генерацию и перехват локально определенного объекта исключения MyExcept ion.
Рассмотрим еще раз конструкцию catch из этого примера:publicvoid SomeFunction(){try{SomeOtherFunctionO ;}catch(MyException me){}}406Часть VII. Дополнительные главыГлавеА если функция SomeOtherFunction () сгенерирует простое исключение Exception или исключение еще какого-то типа, отличного от MyException? Это будет напоминать ситуацию, когда футбольный вратарь ловит баскетбольный мяч — мяч, ловитькоторый он не научен. К счастью, С# позволяет программе определить несколько блоковcatch, каждый из которых предназначен для различного типа исключений.Блоки catch должны в этом случае следовать один за одним, без разрывов, в порядкеот наиболее специализированных классов ко все более общим.
С# проверяет каждыйcatch-блок, последовательно сравнивая сгенерированный объект с аргументами catchолоков, как показано в следующем фрагменте исходного текста:public void SomeFunction ()(trySomeOtherFunctionO ;catch(MyException me) // Наиболее специализированный тип{// исключения// Здесь перехватываются все объекты MyException} // Между этими catch-блоками могут находиться блоки с// другими типами исключенийcatch(Exception е)// Наиболее общий тип исключения// Все остальные неперехваченные исключения// перехватываются в этом блокеЕсли функция SomeOtherFunction () сгенерирует объект Exception, он минуетблок catch (MyException), поскольку Exception не является типом MyException. Он будет перехвачен в следующем блоке — catch (Exception).Любой класс, наследующий MyException, ЯВЛЯЕТСЯ MyException:class MySpecialException : MyException{// ...
что-то там ...}В этом случае блок для MyException перехватит и объект MySpecialException.(Наследование всех пользовательских исключений от одного базового пользовательскогоисключения — неплохая мысль. Само базовое исключение наследуйте от ApplicationException.)Всегда располагайте catch-блоки от наиболее специализированного к наиболее общему. Никогда не размещайте более общий блок первым, как это сделанов приведенном фрагменте исходного текста:public void SomeFunction ()(try{SomeOtherFunction();}Глава 18. Эти исключительные исключения407catch(Exception me) // Самый общий блок - это неверно!{// Все объекты MyException будут перехвачены здесь}catch(MyException е){// Сюда не доберется ни один объект - все они будут// перехвачены более общим блоком}}Более общий блок отнимает объекты исключений у более специализированного блощК счастью, компилятор в состоянии обнаружить такую ошибку и предупредить о ее наличииКак исключения протекают сквозь пальцыЧто, если С#, пройдя все catch-блоки, так и не найдет подходящего? Или в вы»вающей функции вообще нет catch-блока? Что будет тогда?Рассмотрим следующую простую цепочку вызовов функций:// MyException - демонстрация того, как можно создать новый// класс исключения и как функция может перехватывать только// те исключения, которые может обработатьusing System;namespace MyException{// Вводим некоторый тип MyClasspublic class MyClass{}// MyException - - добавляем ссылку на MyClass к// стандартному классу исключенияpublic class MyException : ApplicationExceptionprivate MyClass myobject;public MyException(string sMsg, MyClass mo): base(sMsg)myobject = mo,-}// Дает внешним классам доступ к объектуpublic MyClass MyCustomObject{ get {return myobject;}}public class Program{// fl - - перехватывает обобщенный объект исключенияpublic void f1(bool bExceptionType)try{f2(bExceptionType);catch(Exception e){408Часть VII.
Дополнительные главыConsole.WriteLine("Перехват обобщенного " +"исключения в fl О ") ;Console.WriteLine(е.Message);}}// f2 - - готов к перехвату MyExceptionpublic void f2(bool bExceptionType){try{f3(bExceptionType);}catch(MyExceptionme){Console.WriteLine("Перехват MyException в f 2 ( ) " ) ;Console.WriteLine(me.Message);}}// f3 - - He перехватывает никаких исключенийpublic void f3(bool bExceptionType){ f4(bExceptionType);}// f4 - - генерация одного из двух типов исключенийpublic void f4(bool bExceptionType){// Работаем с некоторым локальным объектомMyClass mc = new MyClass О;if(bExceptionType){// Произошла ошибка — генерируем объект исключения с// объектомthrow new MyException("Генерация MyException " +"в f 4 ( ) " , mc) ;}throw new Exception("Обобщенное исключение в f4 () " );}publicstatic void Main(string[] args){// Сначала генерируем обобщенное исключение...Console.WriteLine("Сначала генерируем " +"обобщенное исключение");new P r o g r a m O .