чение в виде объекта 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 ()
форматирует сообщение об ошибке с описанием ситуации, включая само отрицательное значение, вызвавшее ошибку. Затем функция 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. Дополнительные главы |
Главе |