Инициирование исключений
Выше уже говорилось о том, что метод ProcessFilе просто передает исключение в процедуру Sub Main,
из которой он был вызван. В процедуре Sub Mai n команда вызова тоже заключена в блок Try-Catch, поэтому исключение будет обработано. С другой стороны, такое решение выглядит немного наивно, а если написанные вами классы будут использоваться другими программистами, оно становится попросту опасным. Но даже если дело как-нибудь обойдется, пользователи вашего кода вряд ли будут довольны тем, что вы без разбора передаете исключения, не пытаясь их обработать.
Лучше попытаться по возможности «прибрать» за собой, а затем воспользоваться ключевым словом Throw, чтобы передать объект исключения вызывающей стороне. В главе 4 упоминалось о том, что в VB .NET не поддерживается детерминированное завершение. Следовательно, если вы создали объект с методом D1 spose, этот метод следует вызвать перед тем, как инициировать исключение. Сказанное относится и к открытию файлов, и к получению графического контекста. В следующем фрагменте представлена условная структура подобного кода:
Try
' Создание локального объекта с методом Dispose
' Код. который может инициировать исключения Catch(e As Exception)
local Object.dispose()
Throw e; End Try
Если вы не вызовете метод Dispose для своего локального объекта, то захваченные ресурсы так и не будут освобождены. Ведь ссылка на объект существует лишь в локальном коде; остальные части программы не обладают доступом к методу Dispose! С другой стороны, причина, по которой возникло исключение, остается в силе, поэтому о возникшей проблеме (например, о неудачной операции с файлом) нужно сообщить вызывающему коду. Для этого следует заново инициировать исключение командой Throw, как это сделано во второй выделенной строке.
Впрочем, если вы действительно хотите программировать «как положено», не ограничивайтесь простым перезапуском исключения. Постарайтесь сделать свой код как можно более информативным и включите в объект исключения дополнительную информацию. Для этого есть три возможности.
- Добавьте в исключение
содержательное сообщение и инициируйте его заново. Возможно, новая информация
окажется полезной.
- Инициируйте исключение
одного из стандартных типов, производных от типа текущего исключения, чтобы
оно лучше описывало ситуацию.
- Создайте новый класс
исключения, производный от типа текущего исключения, который будет описывать
ситуацию лучше, чем любой из стандартных классов.
Для примера представьте такую ситуацию: из источника данных читаются пары «ключ/значение», и для последнего ключа не находится парного значения. Программа предполагает, что значение ассоциируется с каждым ключом, поэтому при попытке чтения возникает неожиданно'е исключение ввода-вывода (чтение данных из файла описано в главе 9).
Теперь вы хотите сообщить о происходящем вызывающей стороне. Чтобы добавить в исключение строку, можно воспользоваться специальной версией конструктора класса Exception: Public Sub New(ByVal message As String)
В следующем фрагменте в объект IOException добавляется новая строка с сообщением об отсутствии значения для последнего ключа, после чего исключение инициируется заново.
Dim excep As New IQException("Missing value for last key") Throw excep
Получив инициированное исключение, внешний код получает текст сообщения методом Message класса Exception и узнает о возникшей проблеме.
Вторая ситуация реализуется элементарно благодаря главному правилу наследования: производный класс всегда может использоваться вместо базового класса. Вам лишь остается инициировать исключение производного класса, которое лучше подходит для данной ситуации.
Последний случай требует некоторой дополнительной работы, поскольку для этого потребуется определить класс, производный от существующего класса исключения. Предположим, вы хотите определить новый класс исключения, производный от System. 10. lOException. Новый класс отличается от старого лишь одним ReadOnly-свойством, возвращающим ключ, с которым не ассоциируется парное значение:
Public Class LastValueLostException Inherits System.I0.I0.Exception Private mKey As String Public Sub New(ByVal theKey As String)
MyBase.New("No value found for last key") mKey = theKey End Sub
Public Readonly Property LastKey() As String Get
Return mKey End Get End Property End Class
Обратите внимание: имя созданного класса исключения завершается словом Exception. Это стандартное правило, которому мы настоятельно рекомендуем следовать. Получив исключение LastValueLostException, программист может воспользоваться свойством LastKey, значение которого передается в конструкторе нового класса исключения, и получить ключ, не ассоциируемый со значением. Следующая строка обеспечивает выдачу правильной информации методом Message базового класса Exception: MyBase.New("No value found for last key")
В этой строке вызывается конструктор базового класса (и в конечном счете конструктор предка Exception).
Возможно, вы заметили, что в классе LastValueLostException не переопределяются другие методы — такие, как метод ToString, унаследованный от Exception. В стандартных ситуациях объекты исключений всегда должны выводить стандартные сообщения.
Как использовать созданный класс в программе? Например, если последний ключ без парного значения был равен «oops», исключение будет инициироваться следующей командой:
Throw New LastValueLostException("oops")
Мы создали новый класс исключений, производный от IOExcepti on, потому что потенциальная проблема явно относилась к категории ввода-вывода. Допустим, ситуация имеет более общий характер и для базового класса не существует других очевидных кандидатов, кроме класса Exception. Впрочем, это не совсем верно — лучший выбор существует всегда. Мы настоятельно рекомендуем выбирать в качестве базового не сам класс Exceptlon, а производный от него класс AppllcationException.
Дело в том, что .NET Framework различает исключения, возникшие в результате проблем исполнительной среды (например, нехватки памяти или дискового пространства) и проблем, обусловленных работой вашего приложения. Именно исключения второй категории должны быть производными от AppllcationExcepti on, поэтому именно этот класс следует выбирать базовым при определении обобщенных исключений в программе.
Исполнительная среда помогает сделать следующий шаг. Иерархия исключений расходится на две ветви, показанные на рис. 7.1.
Рис.
7.1. Две основные ветви иерархии исключений
Исключения
как замена для goto
Sub Main()
Dim getData As String
Dim i, j As Integer
Dim e As System.I0.I0Exception
Try
For i = 1 To 10 For j = 1 To 100 Console.WriteC'Type the data, hit the Enter key between " & _
"ZZZ to end: ") getData _ Console.ReadLine() If getData = "ZZZ" Then
e New System.I0.I0Exception("Data entry ended " & _
"at user request") Throw e Else
' Обработка данных End If Next j Next i Catch
Console.WriteLinete.Message) Console. Readline() End Try End Sub
В приведенном выше фрагменте выделенные строки нельзя объединить конструкцией следующего вида:
Dim e As New System.IO.IOException("Data entry ended at user request")
Вследствие правил видимости VB .NET объект исключения окажется недоступным в секции Catch.
При использовании блоков Try-Catch нередко существует код, который должен выполняться как при нормальном завершении, так и при возникновении исключения. Например, в обоих случаях следует закрыть файлы, вызвать методы Dispose и т. д. Даже в простом примере, приведенном в начале главы, потребовалась команда ReadLine, чтобы консольное окно оставалось на экране до нажатия клавиши Enter.
Чтобы некоторый фрагмент выполнялся независимо от того, возникнет ли в программе исключение или нет, в блок Try-Catch включается секция Finally, выделенная в следующем примере жирным шрифтом:
Sub Main()
Dim args(). argument As String
args = Environment. GetCommandLineArgs()
Try
ProcessFile(argsd)) Catch
Console.WriteLine("ERROR") Finally
Console.WriteLine("Press enter to end")
Console.ReadLine() End Try End Sub
Рекомендации
по использованию исключений
- Исключение является
признаком аварийной ситуации; не используйте исключения для простой передачи
информации (мы видели программу, в которой при успешном завершении
функции инициировалось исключение SUCCESS_EXCEPTION).
- Не заменяйте тривиальные
проверки обработкой исключений. Например, исключения не стоит применять для
проверки достижения конца файла (EOF).
- Избегайте раздробленной
обработки исключений, при которой едва ли не каждая команда заключается в
отдельный блок Try-Catch. Заключение всей операции в один блок Try-Catch обычно
предпочтительнее использования нескольких блоков.
- Не поглощайте исключения
конструкциями вида Catch e As Excepti on с пустым блоком команд, если для
этого нет достаточно веских причин. Такая конструкция эквивалентна бездумному
применению On Error Resume в старых программах VB, и пользоваться ею нежелательно
по тем же причинам. Если в программе произошло исключение, обработайте его
или передайте для дальнейшей обработки.
- Последнюю рекомендацию
скорее можно назвать «правилом хорошего тона». Передавая исключение
во внешний код для последующей обработки, добавьте в него новую информацию
(или определите новый класс исключений), чтобы внешний код мог точно определить,
что произошло и какие меры были приняты для того, чтобы исправить ситуацию.