Основы программирования — второй семестр 08-09; Михалкович С.С.; VII часть — различия между версиями
Juliet (обсуждение | вклад) (→Иерархия исключений в .NET) |
Admin (обсуждение | вклад) (→Иерархия исключений в .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''' | ||
− | ''' | + | '''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
то только последним обработчиком.
Замечание. Класс исключения может не быть таким простым. Его можно корректировать в соответствии с уже известными правилами наследования.