1629295403-b876e2087bddebea4bc9666fb2377a02 (846199), страница 80
Текст из файла (страница 80)
fl (false) ;// ... а теперь наше исключениеConsole.WriteLine("\пГенерируем исключение " +"MyException");new P r o g r a m O .fl(true) ;// Ожидаем подтверждения пользователяConsole.WriteLine("Нажмите <Enter> для " +"завершения программы...");Console.Read();Глава 18. Эти исключительные исключения409Функция Main () создает объект Program и тут же использует его для вызова метода f 1 ( ) , который, в свою очередь, вызывает метод f 2 ( ) , который вызывает методf 3 ( ) , вызывающий метод f 4 ( ) .
Функция f 4 () выполняет сложную проверку ошибкикоторая выливается в генерацию либо исключения MyException, либо обобщенногоисключения Exception, в зависимости от аргумента типа bool. Вначале сгенерированное исключение Exception передается в функцию f3 ( ) . Здесь С# не находитcatch-блока, и управление передается вверх по цепочке в функцию f 2 ( ) , которая перехватывает исключения MyException и его наследников.
Этот тип исключения не соответствует обобщенному исключению Exception, и управление передается ещедальше вверх. Наконец, в функции f 1 () находится catch-блок, соответствующий сгенерированному исключению.Второй вызов в функции Main () заставляет функцию f 4 () сгенерировать объект MyException, который перехватывается в функции £2 ( ) . Это исключение не пересылаетсяфункцию f 1 ( ) , поскольку оно перехватывается и обрабатывается функцией f 2 ().(Может ли функция Main () в действительности создать объект класса, содержащийобъект класса, в котором содержится Main () — т.е.
класса Program? Конечно, почемубы и нет? См. последний раздел главы 14, "Интерфейсы и структуры".)Вывод программы выглядит следующим образом:Сначала генерируем обобщенное исключениеПерехват обобщенного исключения в fl()Обобщенное исключение в f4()'Генерируем исключение MyExceptionПерехват MyException в f2()Генерация MyException в f4()Нажмите <Enter> для завершения программы...Функция наподобие f3 ( ) , не содержащая ни одного catch-блока, вовсе не редкость. Можно сказать даже больше — такие функции встречаются гораздо чаще, чемфункции с catch-блоками.
Функция не должна перехватывать исключения, если онане готова их обработать. Должна ли некоторая математическая функция ComputeX()в которой вызывается функция Factorial () как часть вычислений, перехватыватьисключение, которое может быть сгенерировано функцией Factorial () ? ФункцияComputeXOможет совершенно не представлять, откуда взялись неверные входныеданные для функции Factorial О и что теперь делать. В этом случае функция ComputeX ( ) , конечно же, не должна содержать catch-блока и перехватывать генерируемые исключения.Функция наподобие f 2 () перехватывает только один тип исключений.
Она ожидает только один определенный тип ошибки, который умеет обрабатывать. НапримерMyException может быть исключением, определенным для выдающейся библиотекиклассов гениального автора, написанной, понятное дело, мной, и так и называющейс я — BrilliantLibrary. Функции, составляющие BrilliantLibrary, генерируют и перехватывают только исключения MyException.ОднакофункцииBrilliantLibraryмогут такжевызыватьфункцииобычнойстандартной библиотеки System.
Функции BrilliantLibrary могут не знать, какследует обрабатывать обобщенные исключения System, в особенности если они вызваны некорректными входными данными.410Часть VII. ДополнительныеглавыЕсли вы не знаете, что делать с исключением — лучше не делайте ничего: позвольте разобраться с ним вызывающей функции. Но будьте честны сами с собой: не позволяйте исключению уйти только потому, что вы слишком заняты,чтобы писать обработчик.Регенерация исключенияВ ряде случаев метод не в состоянии полностью обработать ошибку, но не хочет передавать исключение вызывающей функции, не вложив свои "пять копеек" в его обработку. В таком случае catch-блок может частично выполнить обработку исключения,а затем передать его дальше (вообще-то, не слишком привлекательная картина).Рассмотрим, например, метод F (), который открывает файл при входе в методс намерением закрыть его при выходе из метода.
Где-то в середине работы F () вызывается G (). Исключение, сгенерированное в G (), может привести к тому, что у F ()не будет шансов закрыть этот файл, который так и останется открытым до полного завершения программы. Идеальным решением было бы включение в F () catch-блока(или блока finally), который бы закрывал все открытые файлы. F() может передать исключение дальше после того, как закроет все необходимые файлы и выполнитпрочие требуемые действия."Регенерировать" исключение можно двумя способами. Один из них состоит в генерации второго исключения с дополнительной (или, как минимум, той же) информациейследующим образом:public void f 1 (){try{f 2 () ;}// Перехват исключения...catch(MyException me){// ...
Частичная обработка исключения ...Console .WriteLine ("Перехват MyException в fl ()")'';// ... Генерация нового исключения для передачи его// вверх по цепочке вызововthrow new Exception("Исключение, сгенерированное в f l ( ) " ) ;Генерация нового объекта исключения позволяет классу переформулироватьсообщение об ошибке, добавив в него дополнительную информацию. Генерация обобщенного объекта Exception вместо специализированного MyException обеспечивает гарантированный перехват этого исключения на уровнях выше f 1 ().Генерация нового исключения имеет тот недостаток, что трассировка стека приэтом начинается заново, с точки генерации нового исключения. Источник исходнойошибки оказывается потерян, если только fit) не предпримет специальных мер дляего сохранения.(пава 18.
Эти исключительные исключения411Второй путь состоит во включении в исходный текст команды throw без аргументачто приводит к генерации того же объекта исключения, как показано в следующеефрагменте исходного текста:publicvoidfl(){try{f 2 () ;}// Перехват исключения...catch(Exception e){// ... Частичная обработка исключения ...Console.WriteLine("Перехват исключения в f l ( ) " ) ;// ... исходное исключение продолжает свой путь по// цепочке вызововthrow;}}Повторная генерация того же объекта имеет свои преимущества и недостатки (ну почему они всегда идут рука об руку?). Регенерация дает возможность промежуточнойфункции перехватить исключение и освободить или закрыть используемые ресурсы, приэтом позволяя объекту исключения донести информацию о месте происшествия доокончательного обработчика этой ошибки.
Однако промежуточные функции не могут(или не должны) добавлять какую-либо информацию, модифицируя объект исключенияперед его повторной генерацией.Как реагировать на исключенияКакие у вас имеются варианты при написании catch-блоков? Как объяснялось ранее, вы можете выполнить одно из следующих трех действий:перехватить исключение;проигнорировать исключение;частично обработать исключение и повторно его сгенерировать (возможно, с добавлением новой информации) либо просто регенерировать его.Но какой стратегии необходимо придерживаться при проектировании системы исключений?В конечном итоге постарайтесь восстановиться после ошибки.
Скорректируйте входные данные, замените их верными, запросите корректные данныеу пользователя — словом, решите вопрос каким-то образом, чтобы можно былопродолжить выполнение программы, как будто ничего не случилось.Если это возможно — используйте транзакционный подход. Откатите все изменения, которые были сделаны к моменту возникновения ошибки, восстановите файлы в исходное состояние и т.д. Главное правило — не испортить пользовательскиеданные. Всегда помогайте пользователю в восстановлении, насколько это возможно. Если даже ничего и не получится, то оставит о вас хорошее впечатление...412Часть VII. Дополнительные главыИногда, когда ошибка не фатальна, достаточно просто поставить пользователя в известность о происшедшем.
Например, вы не смогли открыть указанный пользователем файл. Пользователь может быть раздражен и огорчен этимфактом, но, по крайней мере, он сможет продолжить свою работу.Иногда вы не можете сделать ничего. В этом случае вам остается развести руками, сообщить об этом пользователю и по возможности грациозно завершить работу программы.Обычно это сводится к выводу красивого, соболезнующего, но информативногосообщения об ошибке и завершению программы.
Если возможно, предложитепользователю варианты его действий по исправлению ситуации.Следующий пользовательский класс может сохранить дополнительную информацию,что невозможно в процессе применения стандартных объектов Exception или ApplicationException:// MyException - к стандартному классу исключения добавлена// ссылка на MyClasspublic class MyException : ApplicationException{private MyClass myobject;MyException(string sMsg, MyClass mo): base(sMsg){myobject = m o ;}// Позволяет внешним классам обращаться к сохраненному// в исключении классуpublic MyClass MyObject{ get {return myobject;}}Вернемся вновь к библиотеке функций BrilliantLibrary.
Эти функции знают,как заполнять и считывать новые члены класса MyException, тем самым предоставляяинформацию, необходимую для отслеживания каждой ошибки. Проблема при такомподходе заключается в том, что только функции BrilliantLibrary могут получитьвсе преимущества от использования новых членов MyException.Перекрытие методов, имеющихся у классов Exception или ApplicationException, может предоставить функциям вне BrilliantLibrary доступ к новым данным. Рассмотрим класс исключения из следующей демонстрационной программы CustomException.// CustomException - создание пользовательского исключения,// которое выводит информацию в более дружественном форматеusing System;namespace CustomException{public class CustomException : ApplicationException{private MathClass mathobject;private string sMessage;Глава 18.
Эти исключительные исключения413public CustomException(string sMsg,{mathobject = m o ;sMessage = sMsg;MathClass mo)}override public string Message{get{returnString.Format("Сообщение < { o } > ,Объект {l}",sMessage,mathobj ect.ToString());}override public string ToString(){string s = Message;s += "\пИсключение сгенерировано в ";s += TargetSite.ToString(); // Информация о методе,// сгенерировавшем исключениеreturn s;}}// MathClass - набор созданных мною математических функцийpublic class MathClass{private int nValueOfObject;private string sObjectDescription;public MathClass(string sDescription,int nValue){nValueOfObject = nValue;sObjectDescription = sDescription;public int Value {get {return nValueOfObject;}}// Message - вывод сообщения со значением// присоединенного объекта MathClasspublic string Message{get{return String.Format("({0} = { l } ) " ,sObj ectDescription,nValueOfObject);}}// ToString - расширение нашего пользовательского// свойства Message с использованием Message из базового// класса исключенияoverride public string ToString(){string s = Message + "\n";s +- base.ToString();return s;}// Вычисление обратного значения 1/xpublic double Inverse()414Часть VII.