Основы программирования — второй семестр 08-09; Михалкович С.С.; VII часть

Материал из Вики ИТ мехмата ЮФУ
Версия от 07:43, 7 мая 2018; Admin (обсуждение | вклад) (Иерархия исключений в .NET)

Перейти к: навигация, поиск

Исключения

Введение. Что нам уже известно об исключениях

Впервые мы столкнулись с исключениями в начале курса, когда изучали оператор ввода и обработку ошибок ввода с помощью блока 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)
        IOException (пространство имен System.IO)
           FileNotFoundException
           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

то только последним обработчиком.

Замечание. Класс исключения может не быть таким простым. Его можно корректировать в соответствии с уже известными правилами наследования.