Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

C# для чайников

.pdf
Скачиваний:
198
Добавлен:
27.03.2015
Размер:
15.52 Mб
Скачать

}

finally

{

//Выполнение всех завершающих действий: закрытие

//файлов, освобождение ресурсов и т.п. Этот блок

//выполняется независимо от того, было ли

//сгенерировано исключение.

}

}

public

void

SomeOtherFunction()

 

 

{

 

 

 

 

 

 

 

I I

. . .

Ошибка произошла

где-то в теле

функции . . .

11

. . . Ж "пузырек" исключения "всплывает" вверх по

//

всей

цепочке вызовов,

пока

не будет

перехвачен в

//

блоке

 

catch

 

 

 

throw

new

Exception("Описание

ошибки");

I I

. . .

Продолжение функции . . .

 

Функция Some Function () помещает некоторую часть кода в блок, помеченный ключевым словом try. Любая функция, вызываемая в этом блоке (или функция, вызы­ ваемая функцией, вызываемой в этом блоке — и так далее...), рассматривается как вы­ званная в данном try-блоке.

Непосредственно за блоком try следует ключевое слово catch с блоком, которому передается управление в случае, если где-то в try-блоке произошла ошибка. Аргумент catch-блока — объект класса Exception (или некоторого подкласса Exception).

Однако catch-блок не обязан иметь аргументы: пустой catch перехватывает все

исключения, как и catch(Exception): catch

Если вам не нужен доступ к информации из объекта перехваченного исключения, вы можете указать в блоке только тип исключения:

catch (MyException)

{

// Действия, которые не требуют обращения к объекту // исключения

}

Блок finally— если таковой имеется в вашем исходном тексте — выполняется даже в случае перехвата исключения, не говоря уже о том, что он выполняется при нор­ мальной работе. Обычно он предназначается для "уборки" — закрытия открытых фай­ лов, освобождения ресурсов и т.п.

В отличие от исключений С++, в которых аргументом catch может быть объ­ ект произвольного типа, исключения С# требуют, чтобы он был объектом клас­ са Exception или производного от него.

Итак, где-то в дебрях кода на неизвестно каком уровне вызовов в функции SomeO-

t h e r F u n c t i o n ( )

случилась ошибка... Функция сообщает об этом, генерируя исклю-

Глава 18. Эти

исключительные исключения

401

чение в виде объекта Exception и передает его с помощью оператора throw вверх цепочке вызовов в первый же блок, который в состоянии перехватить его и обработан!

Иногда обработчик try/catch располагается в той же функции, в которой нерировано исключение. Первая же функция, владеющая достаточным количес вом контекстуальной информации для выполнения действий по его обработке может перехватить и обработать его; если данная функция не в состоянии этого сделать, исключение передается дальше вверх по цепочке вызовов.

В демонстрационной программе FactorialException приведены ключевые элементы механизма исключений.

//FactorialException - создание функции вычисления

//факториала, которая сообщает о некорректном аргументе с

//использованием исключений

using System;

namespace FactorialException

{

//MyMathFunctions - набор созданных мною математических

//функций

public class MyMathFunctions

{

//Factorial - возвращает факториал переданного

//аргумента

public static double Factorial(int nValue)

{

// Проверка: отрицательные значения запрещены if (nValue < 0)

{

// Сообщение об отрицательном аргументе string s = String.Format(

"Отрицательный аргумент в вызове Factorial { о } " , nValue);

throw new Exception(s); // Генерация исключения...

}

//начинаем со значения аккумулятора,

//равного 1

double dFactorial = 1.0;

//Цикл со счетчиком nValue, уменьшающимся до 1, с

//умножением на каждой итерации значения аккумулятора

//на величину счетчика

do

{

dFactorial *= nValue,- } while(--nValue > 1);

// Возвращаем вычисленное значение return dFactorial;

402

Часть VII. Дополнительные главн

public class Program

{

public static void Main(string[] args)

{

try // Исключения от функции Factorial() "всплывут" до // этого блока

{

//Вызов функции вычисления факториала в

//цикле от 6 до -6

for (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,

факториал

=

720

i

=

5,

факториал

=

120

i

=

4,

факториал

=

24

i

=

3,

факториал

=

6

i

=

2,

факториал

=

2

i

=

1,

факториал

=

1

i = 0, факториал = 0 Ошибка:

System.Exception: Отрицательный аргумент в вызове Factorial -1

at

Factorial(Int32 nValue) in

 

 

с:\c#programs\Factorial\Program.cs:line

21

at

FactorialException.Program.Main(String[]

args) in

 

с:\c#programs\Factorial\Program.cs:line

49

Нажмите <Enter> для завершения программы...

В первых нескольких строках выводятся корректно вычисленные факториалы число от 6 до 09. Попытка вычислить факториал -1 приводит к генерации исключения.

В первой строке сообщения об ошибке выводится информация, сгенерированная в функции Factorial ( ) . Эта строка описывает природу ошибки, включая вызвавшее

неприятности значение аргумента

 

1.

 

В оставшейся части вывода выполняется трассировка стека. В первой строке указы вается, в какой функции сгенерировано исключение. В данном случае это было сделано в функции Factorial (int) — а именно в 21 строке исходного файла Program.cs Функция Factorial () была вызвана из функции Main (string [] ) в строке 49 того же файла. На этом трассировка файла прекращается, поскольку функция Main () содер­ жит блок, перехвативший и обработавший указанное исключение.

Трассировка стека доступна в одном из окон отладчика Visual Studio.

Вы должны согласиться, что это весьма впечатляюще. Сообщение об ошибке описы­ вает случившееся и позволяет указать аргумент, приведший к ней. Трассировка стека полностью отслеживает, где именно и в результате какой последовательности вызовов произошла ошибка. При такой диагностике поиск ошибки и ее причины не должен со­ ставить никакого труда.

Получение подробной информации, такой как трассировка стека, удобно в про цессе разработки и отладки, но при выходе программы к конечным пользовате­ лям вам наверняка захочется, чтобы она выдавала более простые и понятные со­ общения. (При этом все равно останется возможность записи трассировки стека в журнальный файл, чтобы вы могли владеть всей необходимой информацией, когда пользователь обратится к вам за помощью.)

9 Еще раз напомним читателю, что в математике принято считать, что 0! = 1. — Примеч. ред.

404

Часть VII. Дополнительные главы

Стандартный класс исключения Exception, предоставляемый библиотекой С#, в состоянии предоставить вам достаточное количество информации. Вы можете запро­ сить объект исключения о том, где он был сгенерирован, какая строка была передана ему генерирующей функцией. Однако в ряде случаев стандартного класса Exception быва­ ет недостаточно. У вас может оказаться слишком много информации, чтобы разместить ее в одной строке. Например, функция приложения может захотеть передать вызвавший проблемы объект для последующего анализа. Изучение этого объекта может быть полез­ ным вплоть до полного восстановления после происшедшей ошибки.

Локально определенный класс может наследовать класс Exception так же, как и любой другой класс. Однако пользовательский класс исключения должен наследовать не непосредственно класс Exception, а класс ApplicationException, являющийся подклассом Exception, как показано в следующем фрагменте исходного текста:

//CustomException - добавление ссылки на MyClass к

//стандартному классу исключения

public class CustomException : ApplicationException

{

private MyClass myobject; // Хранит ссылку на вызвавший // проблемы объект

CustomException(string sMsg, MyClass mo) : base(sMsg)

{

myobject = mo;

'}

//Предоставляет доступ к объекту, сохраненному в объекте

//исключения

public MyClass MyCustomObject{ get {return myobject;}}

}

Класс CustomException представляет собой самодельный класс для сообщения об ошибке в любой программе, работающей с классом MyClass. Этот подкласс класса Ap­ plicationException содержит такую же строку, как и исходный класс, но добавляет к ней ссылку на объект MyClass, вызвавший проблемы. Это позволяет произвести де­ тальное исследование случившегося.

В приведенном далее примере выполняется перехват исключения CustomExcep­ tion и используется информация об объекте MyClass:

public class Program

{

public void SomeFunction()

{

try

{

//... действия перед вызовом демонстрационной функции SomeOtherFunctionO ;

//... продолжение работы ...

}

catch(MyException me)

 

[лава{ 18. ЭТИ исключительные исключения

405

//Здесь у вас имеется доступ к методам Exception и

//ApplicationException

string s = me.ToStringО;

// Но у вас есть еще и доступ к методам, уникальным

//для вашего класса исключения MyClass mo = me.MyCustomObject;

//Например, вы можете запросить у объекта MyClass его

//собственное описание

string s = mo.GetDescription();

}

}

public void SomeOtherFunctionO

{

 

 

 

// Создание myobject

 

 

MyClass 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 или Ap­ plicationException, функция SomeFunction О применяет разработанный ваш тип MyExcept ion, пригодный не только для передачи текстового сообщения об ошиб­ ке, но и ссылки на вызвавший ее объект.

Блок catch в функции Main () указывает, что он предназначен для перехвата объ­ ектов MyExcept ion. После того как такой объект перехвачен, код приложения в со­ стоянии применить все методы Exception, как, например, метод ToString ( ) . Одна­ ко в этом catch-блоке может использоваться и другая информация, к примеру, вызов методов объекта MyClass, ссылка на который передана в объекте исключения.

Фрагмент кода в предыдущем разделе продемонстрировал генерацию и перехват ло­ кально определенного объекта исключения MyExcept ion. Рассмотрим еще раз конст­ рукцию catch из этого примера:

public void SomeFunction()

{

try

{

SomeOtherFunctionO ;

}

catch(MyException me)

{

}

}

406

Часть VII. Дополнительные главы

Главе

А если функция 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. Дополнительные главы

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]