Основы программирования — второй семестр 08-09; Михалкович С.С.; VII часть
Содержание
Исключения
Введение. Что нам уже известно об исключениях
Впервые мы столкнулись с исключениями в начале курса, когда изучали оператор ввода и обработку ошибок ввода с помощью блока try..except.
Позже, при работе с файлами, возник термин «исключение». Мы узнали, как обрабатывать исключения с помощью оператора try..except и познакомились с оператором try..finally.
И, наконец, мы научились генерировать собственные исключения.
Теперь пришло время подробнее изучить исключения.
Иерархия исключений в .NET
Стандартные исключения связаны отношением наследования. Базовым классом для всех исключений является класс Exception, который находится в пространстве имен System.
<xh4> Классы исключений в .NET </xh4> Требуется подключить пространство имен System.
Exception ApplicationException Все пользовательские исключения SystemException AccessViolationException (несанкционированный доступ к памяти) ArgumentException (один из передаваемых методу аргументов является недопустимым) ArgumentNullException ArgumentOutOfRangeException ArithmeticException DivideByZeroException (целочисленное деление на 0) IndexOutOfRangeException InvalidCastException (явное приведение к неправильному типу) FormatException (данные не соответствуют формату или форматная строка неправильная) NullReferenceException (исключение, возникающее при попытке разыменования указателя NULL на объект) OutOfMemoryException StackOverflowException KeyNotFoundException (пространство имен System.Collections.Generic) NotImplementedException (метод не реализован) NotSupportedException (метод не поддерживается) System.IO.IOException (пространство имен System.IO) System.IO.FileNotFoundException System.IO.EndOfStreamException (попытка чтения за концом потока)
Секции обработки исключений в блоке try
Полный синтаксис блока try выглядит так:
try ... except on e: Exception1 do <оператор>; on e: Exception2 do <оператор>; ... [else <оператор>] end;
Вопрос: надо ли обрабатывать все возникшие исключения?
Ответ: надо обрабатывать только те, которые понятно, как обрабатывать в данном месте программы. Остальные — не обрабатывать, а считать, что это должен сделать вызывающий код.
Пример 1.
procedure DoAnything(fName: string);
begin
var f: text;
assign(f, fName);
reset(f);
...
end;
Вопрос: надо ли в данном контексте обрабатывать исключение FileNotFinfException?
Попробуем обработать:
try
reset(f);
...
except
// <ничего>
end;
Замечание. Обычно, тот, кто пишет код подпрограммы, которая может генерировать исключение, не знает, что с этими исключениями делать. А тот, кто вызывает — знает.
Поэтому обрабатывать исключение следует только в том месте, где уже известно, что надо делать (в нашем примере — в коде, вызывающем процедуру DoAnything).
Примечание. Раньше мы пользовались Assert:
var f: text;
Assert(FileExists(fName));
...
Недостаток этого способа состоит в том, что конкретное исключение FileNotFoundException заменяется на общее AssertionException, что плохо.
В более развитых средах, как мы уже знаем, вызов Assert не генерируется в версии Release, а только в версии Debug (при разработке). Поэтому в коде могут быть оставлены как Assert, так и обработчики специфических исключений (в версии Debug будет срабатывать Assert, а в версии Release — обработчик исключения).
Пример 2.
function MyMod(a, b: integer): integer;
begin
Result := a - (a div b)*b;
end;
try
readln(a, b);
writeln(MyMod(a, b) div (a-1));
except
on FormatException do
..
on DivideByZeroException do
..
end;
Замечание. Деление на ноль может возникнуть дважды: в функции MyMod и в основной программе.
Вопрос: хотим ли мы отличать эти исключения?
Создание индивидуального класса исключения. Генерация исключений при обработке исключений
Создадим свой класс исключения MyModException, унаследовав его от класса исключений приложений ApplicationException:
uses System;
type
MyModException = class(ApplicationException)
end;
function MyMod(a, b: integer): integer;
begin
try
Result := a - (a div b)*b;
except
raise new MyModException;
end;
end;
begin
var a, b: integer;
try
readln(a, b);
writeln(MyMod(a, b) div (a-1));
except
on FormatException do
writeln('Неверный формат чисел a, b');
on MyModException do
writeln('Ошибка в функции MyMod');
on DivideByZeroException do
writeln('Ошибка деления на ноль в основной программе');
end;
end.
Возможен и другой вариант. Сделаем MyModException наследником класса DivideByZeroException:
type
MyModException = class(DivideByZeroException)
end;
Это позволит сделать следующее: если нужно обработать любые исключения типа «деление на ноль», то достаточно написать обработчик
on DivideByZeroException do
В данном случае
try
readln(a, b);
writeln(MyMod(a, b) div (a-1));
except
on FormatException do
writeln('Неверный формат чисел a, b');
on DivideByZeroException do
writeln('Ошибка деления на ноль');
end;
в секции
on DivideByZeroException do
обрабатываются все исключения-наследники DivideByZeroException, в том числе MyModException.
Вопрос: в каком порядке следует писать обработчики исключений наследников и предков?
Правило. Вначале записываются обработчики классов-потомков, а потом — классов-предков.
Именно поэтому, если и писать
on Exception do
то только последним обработчиком.
Замечание. Класс исключения может не быть таким простым. Его можно корректировать в соответствии с уже известными правилами наследования.