А если функция SomeOtherFunction () сгенерирует простое исключение Excep tion или исключение еще какого-то типа, отличного от MyException? Это будет на поминать ситуацию, когда футбольный вратарь ловит баскетбольный мяч — мяч, ловить который он не научен. К счастью, С# позволяет программе определить несколько блоков catch, каждый из которых предназначен для различного типа исключений.
Блоки catch должны в этом случае следовать один за одним, без разрывов, в порядке от наиболее специализированных классов ко все более общим. С# проверяет каждый catch-блок, последовательно сравнивая сгенерированный объект с аргументами catchолоков, как показано в следующем фрагменте исходного текста:
public void SomeFunction ()
(
t r y
SomeOtherFunctionO ; |
|
|
catch(MyException me) |
// |
Наиболее специализированный тип |
{ |
// |
исключения |
// Здесь перехватываются все объекты MyException
}// Между этими catch-блоками могут находиться блоки с
//другими типами исключений
catch(Exception е) |
// Наиболее общий тип исключения |
//Все остальные неперехваченные исключения
//перехватываются в этом блоке
Если функция SomeOtherFunction () сгенерирует объект Exception, он минует блок catch (MyException), поскольку Exception не является типом MyExcep tion. Он будет перехвачен в следующем блоке — catch (Exception).
Любой класс, наследующий MyException, ЯВЛЯЕТСЯ MyException: class MySpecialException : MyException
{
// ... что-то там ...
}
В этом случае блок для MyException перехватит и объект MySpecialException. (Наследование всех пользовательских исключений от одного базового пользовательского исключения — неплохая мысль. Само базовое исключение наследуйте от ApplicationException.)
Всегда располагайте catch-блоки от наиболее специализированного к наибо лее общему. Никогда не размещайте более общий блок первым, как это сделано в приведенном фрагменте исходного текста:
public void SomeFunction ()
(
t r y
{
SomeOtherFunction();
}
Глава 18. Эти исключительные исключения |
407 |
catch(Exception me) // Самый общий блок - это неверно!
{ |
|
|
|
// |
Все объекты |
MyException |
будут перехвачены здесь |
} |
|
|
|
catch(MyException |
е) |
|
{ |
|
|
|
// |
Сюда не доберется ни один объект - все они будут |
// |
перехвачены |
более общим |
блоком |
}
}
Более общий блок отнимает объекты исключений у более специализированного блощ К счастью, компилятор в состоянии обнаружить такую ошибку и предупредить о ее наличии
Как исключения протекают сквозь пальцы
Что, если С#, пройдя все catch-блоки, так и не найдет подходящего? Или в вы» вающей функции вообще нет catch-блока? Что будет тогда?
Рассмотрим следующую простую цепочку вызовов функций:
// |
MyException - демонстрация того, как можно создать |
новый |
// |
класс исключения и как функция может перехватывать |
только |
// |
те исключения, которые может обработать |
|
using System; namespace MyException
{
//Вводим некоторый тип MyClass public class MyClass{}
//MyException - - добавляем ссылку на MyClass к
//стандартному классу исключения
public class MyException : ApplicationException
private 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 - - готов к перехвату MyException public void f2(bool bExceptionType)
{
try
{
f3(bExceptionType);
}
catch(MyException{ me)
Console.WriteLine("Перехват MyException в f2()"); 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 " + "в f4()", mc) ;
}
throw new Exception("Обобщенное исключение в f4 () " );
}
public{ static void Main(string[] args)
// Сначала генерируем обобщенное исключение...
Console.WriteLine("Сначала генерируем " + "обобщенное исключение");
new ProgramO . fl (false) ;
// ... а теперь наше исключение Console.WriteLine("\пГенерируем исключение " +
"MyException"); new ProgramO .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 () сгенерировать объект MyEx ception, который перехватывается в функции £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 ()
{
t r y
{
f 2 () ;
}
// Перехват исключения...
catch(MyException me)
{
//... Частичная обработка исключения ...
Console .WriteLine ("Перехват MyException в fl ()")'';
//... Генерация нового исключения для передачи его
//вверх по цепочке вызовов
throw new Exception(
"Исключение, сгенерированное в fl()");
Генерация нового объекта исключения позволяет классу переформулировать сообщение об ошибке, добавив в него дополнительную информацию. Генера ция обобщенного объекта Exception вместо специализированного MyEx ception обеспечивает гарантированный перехват этого исключения на уров нях выше f 1 ().
Генерация нового исключения имеет тот недостаток, что трассировка стека при этом начинается заново, с точки генерации нового исключения. Источник исходной ошибки оказывается потерян, если только fit) не предпримет специальных мер для его сохранения.
(пава 18. Эти исключительные исключения |
411 |