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

Материал из Вики ИТ мехмата ЮФУ
Перейти к: навигация, поиск
(Иерархия исключений в .NET)
(Иерархия исключений в .NET)
 
(не показано 15 промежуточных версий 3 участников)
Строка 14: Строка 14:
 
<xh4> Классы исключений в .NET </xh4>
 
<xh4> Классы исключений в .NET </xh4>
 
Требуется подключить пространство имен <tt>System</tt>.
 
Требуется подключить пространство имен <tt>System</tt>.
  '''Exception'''
+
  '''Exception''' (базовый класс исключения)
 +
  ''Свойства'':
 +
  e.Message - текстовое описание ошибки,
 +
  e.StackTrace - последовательность вызовов функций, которая привела к исключению
 
     '''ApplicationException'''
 
     '''ApplicationException'''
 
         Все пользовательские исключения
 
         Все пользовательские исключения
 
     '''SystemException'''
 
     '''SystemException'''
 
         '''AccessViolationException''' (несанкционированный доступ к памяти)
 
         '''AccessViolationException''' (несанкционированный доступ к памяти)
         '''ArgumentException'''
+
         '''ArgumentException''' (один из передаваемых методу аргументов является недопустимым)
 
             '''ArgumentNullException'''
 
             '''ArgumentNullException'''
 
             '''ArgumentOutOfRangeException'''
 
             '''ArgumentOutOfRangeException'''
Строка 26: Строка 29:
 
         '''IndexOutOfRangeException'''
 
         '''IndexOutOfRangeException'''
 
         '''InvalidCastException''' (явное приведение к неправильному типу)
 
         '''InvalidCastException''' (явное приведение к неправильному типу)
         '''FormatException'''  
+
         '''FormatException''' (данные не соответствуют формату или форматная строка неправильная)
         '''NullReferenceException'''
+
         '''NullReferenceException''' (попытка воспользоваться методом, полем или свойством через NULL-ссылку)
 
         '''OutOfMemoryException'''
 
         '''OutOfMemoryException'''
 
         '''StackOverflowException'''
 
         '''StackOverflowException'''
         '''KeyNotFoundException''' (пространство имен System.Collections.Generic)
+
         '''NotImplementedException''' (метод не реализован)
         '''IOException''' (пространство имен System.IO)
+
        '''NotSupportedException''' (метод не поддерживается)
             '''FileNotFoundException'''
+
        '''System.Collections.Generic.KeyNotFoundException'''
             '''EndOfStreamException'''
+
         '''System.IO.IOException''' (пространство имен System.IO)
 +
             '''System.IO.FileNotFoundException'''
 +
             '''System.IO.EndOfStreamException''' (попытка чтения за концом потока)
  
 
=== Секции обработки исключений в блоке try ===
 
=== Секции обработки исключений в блоке try ===
 +
Полный синтаксис блока <tt>'''try'''</tt> выглядит так:
 +
'''try'''
 +
  ...
 +
'''except'''
 +
  '''on''' e: Exception<sub>1</sub> '''do'''
 +
    <оператор>;
 +
  '''on''' e: Exception<sub>2</sub> '''do'''
 +
    <оператор>;
 +
  ...
 +
  ['''else''' <оператор>]
 +
'''end''';
 +
 +
<u>Вопрос</u>: надо ли обрабатывать все возникшие исключения? <br />
 +
<u>Ответ</u>: надо обрабатывать только те, которые понятно, как обрабатывать в данном месте программы. Остальные — не обрабатывать, а считать, что это должен сделать вызывающий код.
 +
 +
''<u>Пример 1</u>.''
 +
<source lang="Delphi">
 +
procedure DoAnything(fName: string);
 +
begin
 +
  var f: text;
 +
  assign(f, fName);
 +
  reset(f);
 +
  ...
 +
end;
 +
</source>
 +
 +
Вопрос: надо ли в данном контексте обрабатывать исключение <tt>FileNotFinfException</tt>? <br />
 +
Попробуем обработать:
 +
<source lang="Delphi">
 +
try
 +
  reset(f);
 +
  ...
 +
except
 +
  // <ничего>
 +
end;
 +
</source>
 +
 +
'''Замечание.''' Обычно, тот, кто пишет код подпрограммы, которая может генерировать исключение, не знает, что с этими исключениями делать. А тот, кто вызывает — знает. <br />
 +
Поэтому обрабатывать исключение следует только в том месте, где уже известно, что надо делать (в нашем примере — в коде, вызывающем процедуру <tt>DoAnything</tt>).
 +
 +
'''Примечание.''' Раньше мы пользовались <tt>Assert</tt>:
 +
<source lang="Delphi">
 +
var f: text;
 +
Assert(FileExists(fName));
 +
...
 +
</source>
 +
 +
Недостаток этого способа состоит в том, что конкретное исключение <tt>FileNotFoundException</tt> заменяется на общее <tt>AssertionException</tt>, что плохо.
 +
 +
В более развитых средах, [[Основы программирования — второй семестр 08-09; Михалкович С.С.; V часть#Генерация собственных исключений | как мы уже знаем]], вызов <tt>Assert</tt> не генерируется в версии '''<tt>Release</tt>''', а только в версии <tt>'''Debug'''</tt> (при разработке). Поэтому в коде могут быть оставлены как <tt>Assert</tt>, так и обработчики специфических исключений (в версии <tt>Debug</tt> будет срабатывать <tt>Assert</tt>, а в версии <tt>Release</tt> — обработчик исключения).
 +
 +
''<u>Пример 2</u>.''
 +
<source lang="Delphi">
 +
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;
 +
</source>
 +
 +
'''Замечание.''' Деление на ноль может возникнуть дважды: в функции <tt>MyMod</tt> и в основной программе. <br />
 +
<u>Вопрос</u>: хотим ли мы отличать эти исключения?
  
 
=== Создание индивидуального класса исключения. Генерация исключений при обработке исключений ===
 
=== Создание индивидуального класса исключения. Генерация исключений при обработке исключений ===
 +
Создадим свой класс исключения <tt>MyModException</tt>, унаследовав его от класса исключений приложений <tt>ApplicationException</tt>:
 +
<source lang="Delphi">
 +
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.
 +
</source>
 +
 +
Возможен и другой вариант. Сделаем <tt>MyModException</tt> наследником класса <tt>DivideByZeroException</tt>:
 +
<source lang="Delphi">
 +
type
 +
  MyModException = class(DivideByZeroException)
 +
  end;
 +
</source>
 +
Это позволит сделать следующее: если нужно обработать любые исключения типа «деление на ноль», то достаточно написать обработчик
 +
<source lang="Delphi">on DivideByZeroException do</source>
 +
В данном случае
 +
<source lang="Delphi">
 +
try
 +
    readln(a, b);
 +
    writeln(MyMod(a, b) div (a-1));
 +
  except
 +
    on FormatException do
 +
      writeln('Неверный формат чисел a, b');
 +
    on DivideByZeroException do
 +
      writeln('Ошибка деления на ноль');
 +
  end;
 +
</source>
 +
в секции
 +
<source lang="Delphi">on DivideByZeroException do</source>
 +
обрабатываются все исключения-наследники <tt>DivideByZeroException</tt>, в том числе <tt>MyModException</tt>.
 +
 +
<u>Вопрос</u>: в каком порядке следует писать обработчики исключений наследников и предков?
 +
 +
'''Правило.''' Вначале записываются обработчики классов-потомков, а потом — классов-предков.
 +
 +
Именно поэтому, если и писать
 +
<source lang="Delphi">on Exception do</source>
 +
то только последним обработчиком.
 +
 +
'''Замечание.''' Класс исключения может не быть таким простым. Его можно корректировать в соответствии с уже известными правилами наследования.
 +
 +
[[Категория:Основы программирования]]

Текущая версия на 08:09, 7 мая 2018

Исключения

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

Впервые мы столкнулись с исключениями в начале курса, когда изучали оператор ввода и обработку ошибок ввода с помощью блока try..except.

Позже, при работе с файлами, возник термин «исключение». Мы узнали, как обрабатывать исключения с помощью оператора try..except и познакомились с оператором try..finally.

И, наконец, мы научились генерировать собственные исключения.

Теперь пришло время подробнее изучить исключения.

Иерархия исключений в .NET

Стандартные исключения связаны отношением наследования. Базовым классом для всех исключений является класс Exception, который находится в пространстве имен System.

<xh4> Классы исключений в .NET </xh4> Требуется подключить пространство имен System.

Exception (базовый класс исключения)
  Свойства:
  e.Message - текстовое описание ошибки, 
  e.StackTrace - последовательность вызовов функций, которая привела к исключению
    ApplicationException
        Все пользовательские исключения
    SystemException
        AccessViolationException (несанкционированный доступ к памяти)
        ArgumentException (один из передаваемых методу аргументов является недопустимым)
           ArgumentNullException
           ArgumentOutOfRangeException
        ArithmeticException
           DivideByZeroException (целочисленное деление на 0)
        IndexOutOfRangeException
        InvalidCastException (явное приведение к неправильному типу)
        FormatException (данные не соответствуют формату или форматная строка неправильная)
        NullReferenceException (попытка воспользоваться методом, полем или свойством через NULL-ссылку)
        OutOfMemoryException
        StackOverflowException
        NotImplementedException (метод не реализован)
        NotSupportedException (метод не поддерживается)
        System.Collections.Generic.KeyNotFoundException
        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

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

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