Класс Exception

Все определяемые на уровне пользователя и системы исключения в конечном итоге всегда наследуются от базового класса System.Exception, который, в свою очередь, наследуется от класса System.Object. Ниже показано, как в целом выглядит этот класс (обратите внимание, что некоторые его члены являются виртуальными и, следовательно, могут переопределяться в производных классах):
Нетрудно заметить, что многие из содержащихся в System.Exception свойств являются по своей природе доступными только для чтения. Это объясняется тем, что для каждого из них значения, используемые по умолчанию, обычно поставляются в производных классах. Например, в производном классе IndexOutOfRangeException поставляется сообщение по умолчанию «Index was outside the bounds of the array» («Индекс вышел за границы массива»).
В следующей таблице приведено краткое описание некоторых наиболее важных свойств класса System.Exception:
| Свойство | Описание |
|---|---|
| Data | Это свойство, доступное только для чтения, позволяет извлекать коллекцию пар «ключ/значение» (представленную объектом, реализующим интерфейс IDictionary), которая предоставляет дополнительную, определяемую программистом, информацию об исключении. По умолчанию эта коллекция является пустой. |
| HelpLink | Это свойство позволяет получать или устанавливать URL-адрес, по которому доступен справочный файл или веб-сайт с детальным описанием ошибки. |
| InnerException | Это свойство, доступное только для чтения, может применяться для получения информации о предыдущем исключении или исключениях, которые послужили причиной возникновения текущего исключения. Запись предыдущих исключений осуществляется путем их передачи конструктору самого последнего исключения. |
| Message | Это свойство, доступное только для чтения, возвращает текстовое описание соответствующей ошибки. Само сообщение об ошибке задается в передаваемом конструктору параметре. |
| Source | Это свойство позволяет получать или устанавливать имя сборки или объекта, который привел к выдаче исключения. |
| StackTrace | Это свойство, доступное только для чтения, содержит строку с описанием последовательности вызовов, которая привела к возникновению исключения. Как нетрудно догадаться, это свойство очень полезно во время отладки или для сохранения информации об ошибке во внешнем журнале ошибок. |
| TargetSite | Это свойство, доступное только для чтения, возвращает объект MethodBase с описанием многочисленных деталей метода, который привел к выдаче исключения (вызов вместе с ним ToString() позволяет идентифицировать этот метод по имени) |
Давайте рассмотрим пример:

Как видите в данном примере используется свойство Message, класса Exception, для вывода информации о возникшем исключении. Более подробно свойства класса Exception рассматриваются в следующей статье.
Name already in use
csharp_for_beginners / c13.md
- Go to file T
- Go to line L
- Copy path
- Copy permalink
- Open with Desktop
- View raw
- Copy raw contents Copy raw contents
Copy raw contents
Copy raw contents
ГЛАВА 13. Обработка исключительных ситуаций
Исключительная ситуация, или просто исключение, происходит во время выполнения. Используя под систему обработки исключительных ситуаций в С#, можно обрабатывать структурированным и контроли руемым образом ошибки, возникающие при выполнении программы. Главное преимущество обработки исключи тельных ситуаций заключается в том, что она позволяет ав томатизировать получение большей части кода, который раньше приходилось вводить в любую крупную програм му вручную для обработки ошибок. Так, если программа написана на языке программирования без обработки ис ключительных ситуаций, то при неудачном выполнении методов приходится возвращать коды ошибок, которые не обходимо проверять вручную при каждом вызове метода. Это не только трудоемкий, но и чреватый ошибками про цесс. Обработка исключительных ситуаций рационализи рует весь процесс обработки ошибок, позволяя определить в программе блок кода, называемый обработчиком исклю чений и выполняющийся автоматически, когда возникает ошибка. Эго избавляет от необходимости проверять вруч ную, насколько удачно или неудачно завершилась конкрет ная операция либо вызов метода. Если возникнет ошибка, она будет обработана соответствующим образом обработ чиком ошибок.
Обработка исключительных ситуаций важна еще и по тому, что в С# определены стандартные исключения для типичных программных ошибок, например деление на нуль или выход индекса за границы массива. Для реаги рования на подобные ошибки в программе должно быть организовано отслеживание и обработка соответствующих исключительных ситуаций. Ведь в конечном счете для успешного программирования на C# необходимо научиться умело пользоваться подсистемой обработки исключи тельных ситуаций.
В C# исключения представлены в виде классов. Все классы исключений должны быть производными от встроенного в C# класса Exception, являющегося частью про странства имен System. Следовательно, все исключения являются подклассами класса Exception.
К числу самых важных подклассов Exception относится класс SystemException. Именно от этого класса являются производными все исключения, генерируемые испол няющей системой C# (т.е. системой CLR). Класс SystemException ничего не добавляет к классу Exception, а просто определяет вершину иерархии стандартных исключений.
В среде .NET Framework определено несколько встроенных исключений, являю щихся производными от класса SystemException. Например, при попытке выпол нить деление на нуль генерируется исключение DivideByZeroException. Как будет показано далее в этой главе, в C# можно создавать собственные классы исключений, производные от класса Exception.
Основы обработки исключительных ситуаций
Обработка исключительных ситуаций в C# организуется с помощью четырех клю чевых слов: try, catch, throw и finally. Они образуют взаимосвязанную подсистему, в которой применение одного из ключевых слов подразумевает применение другого. На протяжении всей этой главы назначение и применение каждого из упомянутых выше ключевых слов будет рассмотрено во всех подробностях. Но прежде необходимо дать общее представление о роли каждого из них в обработке исключительных ситуа ций. Поэтому ниже кратко описан принцип их действия.
Операторы программы, которые требуется контролировать на появление исключе ний, заключаются в блок try. Если внутри блока try возникает исключительная ситуа ция, генерируется исключение. Это исключение может быть перехвачено и обработано каким-нибудь рациональным способом в коде программы с помощью оператора, обо значаемого ключевым словом catch. Исключения, возникающие на уровне системы, генерируются исполняющей системой автоматически. А для генерирования исключе ний вручную служит ключевое слово throw. Любой код, который должен быть непре менно выполнен после выхода из блока try, помещается в блок finally.
Применение пары ключевых слов try и catch
Основу обработки исключительных ситуаций в C# составляет пара ключевых слов try и catch. Эти ключевые слова действуют совместно и не могут быть использованы порознь. Ниже приведена общая форма определения блоков try/catch для обработ ки исключительных ситуаций:
где ЕхсерТуре — это тип возникающей исключительной ситуации. Когда исключение генерируется оператором try, оно перехватывается составляющим ему пару опера тором catch, который затем обрабатывает это исключение. В зависимости от типа исключения выполняется и соответствующий оператор catch. Так, если типы гене рируемого исключения и того, что указывается в операторе catch, совпадают, то вы полняется именно этот оператор, а все остальные пропускаются. Когда исключение перехватывается, переменная исключения exOb получает свое значение.
На самом деле указывать переменную ехОb необязательно. Так, ее необязательно указывать, если обработчику исключений не требуется доступ к объекту исключения, что бывает довольно часто. Для обработки исключения достаточно и его типа. Именно поэтому во многих примерах программ, приведенных в этой главе, переменная ехОb опускается.
Следует, однако, иметь в виду, что если исключение не генерируется, то блок опера тора try завершается как обычно, и все его операторы catch пропускаются. Выполне ние программы возобновляется с первого оператора, следующего после завершающе го оператора catch. Таким образом, оператор catch выполняется лишь в том случае, если генерируется исключение.
Простой пример обработки исключительной ситуации
Рассмотрим простой пример, демонстрирующий отслеживание и перехватывание исключения. Как вам должно быть уже известно, попытка индексировать массив за его границами приводит к ошибке. Когда возникает подобная ошибка, система CLR гене рирует исключение IndexOutOfRangeException, которое определено как стандарт ное для среды .NET Framework. В приведенной ниже программе такое исключение генерируется намеренно и затем перехватывается.
При выполнении этой программы получается следующий результат.
В данном примере массив nums типа int состоит из четырех элементов. Но в цикле for предпринимается попытка проиндексировать этот массив от 0 до 9, что и приво дит к появлению исключения IndexOutOfRangeException, когда происходит обра щение к элементу массива по индексу 4.
Несмотря на всю свою краткость, приведенный выше пример наглядно демон стрирует ряд основных моментов процесса обработки исключительных ситуаций. Во-первых, код, который требуется контролировать на наличие ошибок, содержится в блоке try. Во-вторых, когда возникает исключительная ситуация (в данном случае — при попытке проиндексировать массив nums за его границами в цикле for), в блоке try генерируется исключение, которое затем перехватывается в блоке catch. В этот момент выполнение кода в блоке try завершается и управление передается блоку catch. Это означает, что оператор catch не вызывается специально, а выполнение кода переходит к нему автоматически. Следовательно, оператор, содержащий метод WriteLine() и следующий непосредственно за циклом for, где происходит выход индекса за границы массива, вообще не выполняется. А в задачу обработчика исклю чений входит исправление ошибки, приведшей к исключительной ситуации, чтобы продолжить выполнение программы в нормальном режиме.
Обратите внимание на то, что в операторе catch указан только тип исключения (в данном случае — IndexOutOfRangeException), а переменная исключения отсут ствует. Как упоминалось ранее, переменную исключения требуется указывать лишь в том случае, если требуется доступ к объекту исключения. В ряде случаев значение объекта исключения может быть использовано обработчиком исключений для по лучения дополнительной информации о самой ошибке, но зачастую для обработки исключительной ситуации достаточно просто знать, что она произошла. Поэтому переменная исключения нередко отсутствует в обработчиках исключений, как в рас сматриваемом здесь примере.
Как пояснялось ранее, если исключение не генерируется в блоке try, то блок catch не выполняется, а управление программой передается оператору, следующему после блока catch. Для того чтобы убедиться в этом, замените в предыдущем примере про граммы строку кода
Теперь индексирование массива не выходит за его границы в цикле for. Следова тельно, никакого исключения не генерируется и блок catch не выполняется.
Второй пример обработки исключительной ситуации
Следует особо подчеркнуть, что весь код, выполняемый в блоке try, контролирует ся на предмет исключительных ситуаций, в том числе и тех, которые могут возникнуть в результате вызова метода из самого блока try. Исключение, генерируемое методом в блоке try, может быть перехвачено в том же блоке, если, конечно, этого не будет сделано в самом методе.
В качестве еще одного примера рассмотрим следующую программу, где блок try помещается в методе Main(). Из этого блока вызывается метод GenException(), в ко тором и генерируется исключение IndexOutOfRangeException. Это исключение не перехватывается методом GenException(). Но поскольку метод GenException() вы зывается из блока try в методе Main(), то исключение перехватывается в блоке catch, связанном непосредственно с этим блоком try.
Выполнение этой программы дает такой же результат, как и в предыдущем примере.
Как пояснялось выше, метод GenException() вызывается из блока try, и поэтому генерируемое им исключение перехватывается не в нем, а в блоке catch внутри мето да Main(). А если бы исключение перехватывалось в методе GenException(), оно не было бы вообще передано обратно методу Main().
Последствия неперехвата исключений
Перехват одного из стандартных исключений, как в приведенных выше примерах, дает еще одно преимущество: он исключает аварийное завершение программы. Как только исключение будет сгенерировано, оно должно быть перехвачено каким-то фраг ментом кода в определенном месте программы. Вообще говоря, если исключение не перехватывается в программе, то оно будет перехвачено исполняющей системой. Но дело в том, что исполняющая система выдаст сообщение об ошибке и прервет выпол нение программы. Так, в приведенном ниже примере программы исключение в связи с выходом индекса за границы массива не перехватывается.
Когда возникает ошибка индексирования массива, выполнение программы преры вается и выдается следующее сообщение об ошибке.
Это сообщение уведомляет об обнаружении в методе NotHandled.Main() необра ботанного исключения типа System.IndexOutOfRangeException, которое связано с выходом индекса за границы массива.
Такие сообщения об ошибках полезны для отладки программы, но, по меньше мере, нежелательны при ее использовании на практике! Именно поэтому так важно организовать обработку исключительных ситуаций в самой программе.
Как упоминалось ранее, тип генерируемого исключения должен соответствовать типу, указанному в операторе catch. В противном случае исключение не будет пере хвачено. Например, в приведенной ниже программе предпринимается попытка пере хватить ошибку нарушения границ массива в блоке catch, реагирующем на исключе ние DivideByZeroException, связанное с делением на нуль и являющееся еще одним стандартным исключением. Когда индексирование массива выходит за его границы, генерируется исключение IndexOutOfRangeException, но оно не будет перехвачено блоком catch, что приведет к аварийному завершению программы.
Вот к какому результату приводит выполнение этой программы.
Как следует из приведенного выше результата, в блоке catch, реагирующем на исключение DivideByZeroException, не удалось перехватить исключение IndexOutOfRangeException.
Обработка исключительных ситуаций — “изящный” способ устранения программных ошибок
Одно из главных преимуществ обработки исключительных ситуаций заключается в том, что она позволяет вовремя отреагировать на ошибку в программе и затем про должить ее выполнение. В качестве примера рассмотрим еще одну программу, в кото рой элементы одного массива делятся на элементы другого. Если при этом происходит деление на нуль, то генерируется исключение DivideByZeroException. Обработка подобной исключительной ситуации заключается в том, что программа уведомляет об ошибке и затем продолжает свое выполнение. Таким образом, попытка деления на нуль не приведет к аварийному завершению программы из-за ошибки при ее вы полнении. Вместо этого ошибка обрабатывается «изящно», не прерывая выполнение программы.
Ниже приведен результат выполнения этой программы.
Из данного примера следует еще один важный вывод: как только исключение обра ботано, оно удаляется из системы. Поэтому в приведенной выше программе проверка ошибок в блоке try начинается снова на каждом шаге цикла for, при условии, что все предыдущие исключительные ситуации были обработаны. Это позволяет обрабаты вать в программе повторяющиеся ошибки.
Применение нескольких операторов catch
С одним оператором try можно связать несколько операторов catch. И на прак тике это делается довольно часто. Но все операторы catch должны перехватывать ис ключения разного типа. В качестве примера ниже приведена программа, в которой перехватываются ошибки выхода за границы массива и деления на нуль.
Вот к какому результату приводит выполнение этой программы.
Как следует из приведенного выше результата, каждый оператор catch реагирует только на свой тип исключения.
Вообще говоря, операторы catch выполняются по порядку их следования в про грамме. Но при этом выполняется только один блок catch, в котором тип исклю чения совпадает с типом генерируемого исключения. А все остальные блоки catch пропускаются.
Перехват всех исключений
Время от времени возникает потребность в перехвате всех исключений независимо от их типа. Для этой цели служит оператор catch, в котором тип и переменная ис ключения не указываются. Ниже приведена общая форма такого оператора.
С помощью такой формы создается «универсальный» обработчик всех исключе ний, перехватываемых в программе.
Ниже приведен пример такого «универсального» обработчика исключений. Об ратите внимание на то, что он перехватывает и обрабатывает оба исключения, IndexOutOfRangeException и DivideByZeroException, генерируемых в программе.
При выполнении этой программы получается следующий результат.
Применяя «универсальный» перехват, следует иметь в виду, что его блок должен располагаться последним по порядку среди всех блоков catch.
ПРИМЕЧАНИЕ В подавляющем большинстве случаев «универсальный» обработчик исключений (не при меняется. Как правило, исключения, которые могут быть сгенерированы в коде, обрабаты ваются по отдельности. Неправильное использование “универсального” обработчика может привести к тому, что ошибки, перехватывавшиеся при тестировании программы, маскируют ся. Кроме того, организовать надлежащую обработку всех исключительных ситуаций в одном обработчике не так-то просто. Иными словами, “универсальный» обработчик исключений может оказаться пригодным лишь в особых случаях, например в инструментальном средстве анализа кода во время выполнения.
Вложение блоков try
Один блок try может быть вложен в другой. Исключение, генерируемое во вну треннем блоке try и не перехваченное в соответствующем блоке catch, передается во внешний блок try. В качестве примера ниже приведена программа, в которой исклю чение IndexOutOfRangeException перехватывается не во внутреннем, а во внешнем блоке try.
Выполнение этой программы приводит к следующему результату.
В данном примере исключение, обрабатываемое во внутреннем блоке try и связан ное с ошибкой из-за деления на нуль, не мешает дальнейшему выполнению програм мы. Но ошибка нарушения границ массива, обнаруживаемая во внешнем блоке try, приводит к прерыванию программы.
Безусловно, приведенный выше пример демонстрирует далеко не единственное основание для применения вложенных блоков try, тем не менее из него можно сделать важный общий вывод. Вложенные блоки try нередко применяются для обработки раз личных категорий ошибок разными способами. В частности, одни ошибки считаются неисправимыми и не подлежат исправлению, а другие ошибки незначительны и могут быть обработаны немедленно. Как правило, внешний блок try служит для обнаруже ниям обработки самых серьезных ошибок, а во внутренних блоках try обрабатываются менее серьезные ошибки. Кроме того, внешний блок try может стать «универсальным» для тех ошибок, которые не подлежат обработке во внутреннем блоке.
Генерирование исключений вручную
В приведенных выше примерах перехватывались исключения, генерировавшиеся исполняющей системой автоматически. Но исключение может быть сгенерировано и вручную с помощью оператора throw. Ниже приведена общая форма такого гене рирования:
где в качестве exceptOb должен быть обозначен объект класса исключений, произво дного от класса Exception.
Ниже приведен пример программы, в которой демонстрируется применение опе ратора throw для генерирования исключения DivideByZeroException.
Вот к какому результату приводит выполнение этой программы.
Обратите внимание на то, что исключение DivideByZeroException было сге нерировано с использованием ключевого слова new в операторе throw. Не следует забывать, что в данном случае генерируется конкретный объект, а следовательно, он должен быть создан перед генерированием исключения. Это означает, что сгенериро вать исключение только по его типу нельзя. В данном примере для создания объекта DivideByZeroException был автоматически вызван конструктор, используемый по умолчанию, хотя для генерирования исключений доступны и другие конструкторы.
Повторное генерирование исключений
Исключение, перехваченное в одном блоке catch, может быть повторно сгенери ровано в другом блоке, чтобы быть перехваченным во внешнем блоке catch. Наиболее вероятной причиной для повторного генерирования исключения служит предоставле ние доступа к исключению нескольким обработчикам. Допустим, что один обработчик оперирует каким-нибудь одним аспектом исключения, а другой обработчик — другим его аспектом. Для повторного генерирования исключения достаточно указать опера тор throw без сопутствующего выражения, как в приведенной ниже форме.
Не следует, однако, забывать, что когда исключение генерируется повторно, то оно не перехватывается снова тем же самым блоком catch, а передается во внешний блок catch.
В приведенном ниже примере программы демонстрируется повтор ное генерирование исключения. В данном случае генерируется исключение IndexOutOfRangeException.
В этом примере программы ошибки из-за деления на нуль обрабатываются локаль но в методе GenException(), но ошибка выхода за границы массива генерируется повторно. В данном случае исключение IndexOutOfRangeException обрабатывается в методе Main().
Использование блока finally
Иногда требуется определить кодовый блок, который будет выполняться после вы хода из блока try/catch. В частности, исключительная ситуация может возникнуть в связи с ошибкой, приводящей к преждевременному возврату из текущего метода. Но в этом методе мог быть открыт файл, который нужно закрыть, или же установлено сетевое соединение, требующее разрывания. Подобные ситуации нередки в програм мировании, и поэтому для их разрешения в C# предусмотрен удобный способ: вос пользоваться блоком finally.
Для того чтобы указать кодовый блок, который должен выполняться после блока try/catch, достаточно вставить блок finally в конце последовательности операторов try/catch. Ниже приведена общая форма совместного использования блоков try/ catch и finally.
Блок finally будет выполняться всякий раз, когда происходит выход из блока try/ catch, независимо от причин, которые к этому привели. Это означает, что если блок try завершается нормально или по причине исключения, то последним выполняется код, определяемый в блоке finally. Блок finally выполняется и в том случае, если любой код в блоке try или в связанных с ним блоках catch приводит к возврату из метода.
Ниже приведен пример применения блока finally.
Вот к какому результату приводит выполнение этой программы.
Как следует из приведенного выше результата, блок finally выполняется независи мо от причины выхода из блока try.
И еще одно замечание: с точки зрения синтаксиса блок finally следует после блока try, и формально блоки catch для этого не требуются. Следовательно, блок finally можно ввести непосредственно после блока try, опустив блоки catch. В этом случае блок finally начнет выполняться сразу же после выхода из блока try, но исключения обрабатываться не будут.
Подробное рассмотрение класса Exception
В приведенных выше примерах исключения только перехватывались, но никакой существенной обработке они не подвергались. Как пояснялось выше, в операторе catch допускается указывать тип и переменную исключения. Переменная получает ссылку на объект исключения. Во всех исключениях поддерживаются члены, опреде ленные в классе Exception, поскольку все исключения являются производными от этого класса. В этом разделе будет рассмотрен ряд наиболее полезных членов и кон структоров класса Exception и приведены конкретные примеры использования пере менной исключения.
В классе Exception определяется ряд свойств. К числу самых интересных отно сятся три свойства: Message, StackTrace и TargetSite. Все эти свойства доступны только для чтения. Свойство Message содержит символьную строку, описывающую характер ошибки; свойство StackTrace — строку с вызовами стека, приведшими к ис ключительной ситуации, а свойство TargetSite получает объект, обозначающий ме тод, сгенерировавший исключение.
Кроме того, в классе Exception определяется ряд методов. Чаще всего приходится пользоваться методом ToString(), возвращающим символьную строку с описанием исключения. Этот метод автоматически вызывается, например, при отображении ис ключения с помощью метода WriteLine().
Применение всех трех упомянутых выше свойств и метода из класса Exception демонстрируется в приведенном ниже примере программы.
При выполнении этой программы получается следующий результат.
В классе Exception определяются четыре следующих конструктора.
Первый конструктор используется по умолчанию. Во втором конструкторе ука зывается строка сообщение, связанная со свойством Message, которое имеет отно шение к генерируемому исключению. В третьем конструкторе указывается так на зываемое внутреннее исключение. Этот конструктор используется в том случае, когда одно исключение порождает другое, причем внутреннее_исключение обозначает первое исключение, которое будет пустым, если внутреннее исключение отсутствует. (Если внутреннее исключение присутствует, то оно может быть получено из свойства InnerException, определяемого в классе Exception.) И последний конструктор об рабатывает исключения, происходящие дистанционно, и поэтому требует десериали зации.
Следует также заметить, что в четвертом конструкторе класса Exception типы SerializationInfo и StreamingContext относятся к пространству имен System. Runtime.Serialization.
Наиболее часто используемые исключения
В пространстве имен System определено несколько стандартных, встроенных ис ключений. Все эти исключения являются производными от класса SystemException, поскольку они генерируются системой CLR при появлении ошибки во время выпол нения. В табл. 13.1 перечислены некоторые наиболее часто используемые стандартные исключения.
Таблица 13.1. Наиболее часто используемые исключения, определенные в пространстве имен System
| Исключение | Значение |
|---|---|
| ArrayTypeMismatchException | Тип сохраняемого значения несовместим с типом массива |
| DivideByZeroException | Попытка деления на нуль |
| IndexOutOfRangeException | Индекс оказался за границами массива |
| InvalidCastException | Неверно выполнено динамическое приведение типов |
| OutOfMemoryException | Недостаточно свободной памяти для дальнейшего выполнения программы. Это исключение может быть, например, сгенерировано, если для создания объекта с помощью оператора new не хватает памяти |
| OverflowException | Произошло арифметическое переполнение |
| NullReferenceException | Попытка использовать пустую ссылку, т.е. ссылку, которая не указывает ни на один из объектов |
Большинство исключений, приведенных в табл. 13.1, не требует особых пояснений, кроме исключения NullReferenceException. Это исключение генерируется при по пытке использовать пустую ссылку на несуществующий объект, например, при вы зове метода по пустой ссылке. Пустой называется такая ссылка, которая не указывает ни на один из объектов. Для того чтобы создать такую ссылку, достаточно, например, присвоить явным образом пустое значение переменной ссылочного типа, используя ключевое слово null. Пустые ссылки могут также появляться и другими, менее оче видными путями. Ниже приведен пример программы, демонстрирующий обработку исключения NullReferenceException.
Вот к какому результату приводит выполнение этой программы.
В приведенном выше примере программы создается класс X, в котором определя ются член х и метод Add(), складывающий значение члена х в вызывающем объекте со значением члена х в объекте, передаваемом этому методу в качестве параметра. Оба объекта класса X создаются в методе Main(). Первый из них (переменная р) инициа лизируется, а второй (переменная q) — нет. Вместо этого переменной q присваивается пустое значение. Затем вызывается метод р.Add() с переменной q в качестве аргумен та. Но поскольку переменная q не ссылается ни на один из объектов, то при попытке получить значение члена q.х генерируется исключение NullReferenceException.
Получение производных классов исключений
Несмотря на то что встроенные исключения охватывают наиболее распространен ные программные ошибки, обработка исключительных ситуаций в C# не ограничива ется только этими ошибками. В действительности одна из сильных сторон принятого в C# подхода к обработке исключительных ситуаций состоит в том, что в этом языке допускается использовать исключения, определяемые пользователем, т.е. тем, кто про граммирует на С#. В частности, такие специальные исключения можно использовать для обработки ошибок в собственном коде, а создаются они очень просто. Для этого достаточно определить класс, производный от класса Exception. В таких классах со всем не обязательно что-то реализовывать — одного только их существования в систе ме типов уже достаточно, чтобы использовать их в качестве исключений.
ПРИМЕЧАНИЕ В прошлом специальные исключения создавались как производные от класса Application.Exception, поскольку эта иерархия классов была первоначально зарезер вирована для исключений прикладного характера. Но теперь корпорация Microsoft не реко мендует этого делать, а вместо этого получать исключения, производные от класса Exception. Именно по этой причине данный подход и рассматривается в настоящей книге.
Создаваемые пользователем классы будут автоматически получать свойства и мето ды, определенные в классе Exception и доступные для них. Разумеется, любой из этих членов класса Exception можно переопределить в создаваемых классах исключений. Когда создается собственный класс исключений, то, как правило, желательно, что бы в нем поддерживались все конструкторы, определенные в классе Exception. В про стых специальных классах исключений этого нетрудно добиться, поскольку для этого достаточно передать подходящие аргументы соответствующему конструктору класса Exception, используя ключевое слово base. Но формально нужно предоставить толь ко те конструкторы, которые фактически используются в программе.
Рассмотрим пример программы, в которой используется исключение специального типа. Напомним, что в конце главы 10 был разработан класс RangeArray, поддержи вающий одномерные массивы, в которых начальный и конечный индексы определяют ся пользователем. Так, например, вполне допустимым считается массив, индексируе мый в пределах от -5 до 27. Если же индекс выходил за границы массива, то для обра ботки этой ошибки в классе RangeArray была определена специальная переменная. Такая переменная устанавливалась и проверялась после каждой операции обращения к массиву в коде, использовавшем класс RangeArray. Безусловно, такой подход к об работке ошибок «неуклюж» и чреват дополнительными ошибками. В приведенном ниже улучшенном варианте класса RangeArray обработка ошибок нарушения границ массива выполняется более изящным и надежным способом с помощью специально генерируемого исключения.
// Использовать специальное исключение для обработки // ошибок при обращении к массиву класса RangeArray. using System;
Класс Exception и его свойства. Наиболее частые виды исключений в C#
На данный момент мы знаем достаточно про классы, наследование в C#, а также перехват и обработку исключений в C#, чтобы более глубоко погрузиться в тему обработки исключений в C# и рассмотреть основные свойства класса Exception , а также наиболее распространенные типы исключений и примеры их возникновения в приложениях.
Класс Exception — базовый класс всех типов исключений в C#
Как мы уже знаем, родителем всех классов в C# является класс System.Object . Так же, как Object является базовым для всех классов, так и Exception является базовым классом для всех абсолютно типов исключений в C#. Посмотрим, какие свойства есть у этого класса.
Свойство Message
Message — свойство, которое предоставляет сведения о причине возникновения исключения. Например:
Выведет в консоль сообщение «Попытка деления на нуль.», если мы попытаемся поделить число на нуль, как было продемонстрировано в этой статье.
Свойство Message является виртуальным (virtual), поэтому может быть переопределено в классах-наследниках.
Свойство Source
Source — свойство, которое возвращает или задает имя приложения или объекта, вызывавшего ошибку. Например:
выведет в консоль название вашего приложения. Также, как и Message свойство виртуальное.
Свойство InnerException
InnerException — свойство может использоваться для создания и сохранения последовательностей исключений во время обработки текущего исключения. По умолчанию содержит информацию об исключении, которое послужило причиной текущего исключения. Например:
В случае, если текущее исключение содержит информацию об исключении более высокого уровня ( исключении, которое послужило причиной текущего), то на экран будет выведено сообщение от исключения верхнего уровня. Обратите внимание, что не всегда InnerException , содержит какой-либо объект, поэтому в блоке catch был применен фильтр. В отличие от предыдущих свойств, это свойство нельзя переопределить в наследниках.
Свойство StackTrace
StackTrace — виртуальное свойство, которое содержит трассировку стека, которую можно использовать для определения места возникновения ошибки. Например:
В консоль выведет строку примерно такого содержания:
где XXXXX — название вашего приложения; YYYYYY — полный путь к файлу Program.cs или другому, в котором произошло исключение; ZZZZZ — номер строки на которой возникло исключение.
Свойство TargetSite
TargetSite — возвращает метод, в котором и было вызвано исключение. Например:
выведет в консоль строку:
Свойство TargetSite нельзя переопределять в наследниках.
Наиболее распространенные типы исключений
Так как Exception — базовый класс для всех исключений, то, например, вот такой код:
Полностью эквивалентен вот такому
со всеми вытекающими последствиями о которых было рассказано в этой части статьи. Вместе с этим, в C# имеется достаточно большое количество типов исключений, специализирующихся на конкретных видах. Например, тот же тип DivideByZeroException о котором мы довольно подробно говорили. Рассмотрим наиболее часто встречающиеся типы исключений и примеры их появления в приложениях.
IndexOutOfRangeException
Вызывается средой выполнения только при неправильной индексации массива, например, при индексирование массива вне допустимого диапазона. Следующий код демонстрирует возникновение такого типа исключения:
NullReferenceException
Вызывается средой выполнения только в том случае, если имеется ссылка на пустой объект, например:
здесь мы пробуем преобразовать пустой объект в строку — неизбежно появление исключения NullReferenceException .
ArgumentNullException
Вызывается методами, которые не допускают пустой аргумент, например
строке s присвоено значение null . Вызов метода строки IndexOf приведет к вызову исключения ArgumentNullException .
ArgumentOutOfRangeException
Вызывается методами, проверяющими попадание аргументов в заданный диапазон, например:
Здесь мы пробуем извлечь из строки подстроку, начиная с индекса, который явно выходит за диапазон (за размер строки — Length ) — неизбежно возникновение исключения ArgumentOutOfRangeException
Итого
Сегодня мы рассмотрели основные свойства класса Exception — базового класса всех типов исключений в C#, а также познакомились с некоторыми наиболее часто встречающимися типами исключений в C#. Практически каждый тип исключений содержит какую-либо информацию об исключении в своих собственных свойствах и позволяет делать обработку исключений более информативной.
Exception c что это
Базовым для всех типов исключений является тип Exception . Этот тип определяет ряд свойств, с помощью которых можно получить информацию об исключении.
InnerException : хранит информацию об исключении, которое послужило причиной текущего исключения
Message : хранит сообщение об исключении, текст ошибки
Source : хранит имя объекта или сборки, которое вызвало исключение
StackTrace : возвращает строковое представление стека вызывов, которые привели к возникновению исключения
TargetSite : возвращает метод, в котором и было вызвано исключение
Например, обработаем исключения типа Exception:

Однако так как тип Exception является базовым типом для всех исключений, то выражение catch (Exception ex) будет обрабатывать все исключения, которые могут возникнуть.
Но также есть более специализированные типы исключений, которые предназначены для обработки каких-то определенных видов исключений. Их довольно много, я приведу лишь некоторые:
DivideByZeroException : представляет исключение, которое генерируется при делении на ноль
ArgumentOutOfRangeException : генерируется, если значение аргумента находится вне диапазона допустимых значений
ArgumentException : генерируется, если в метод для параметра передается некорректное значение
IndexOutOfRangeException : генерируется, если индекс элемента массива или коллекции находится вне диапазона допустимых значений
InvalidCastException : генерируется при попытке произвести недопустимые преобразования типов
NullReferenceException : генерируется при попытке обращения к объекту, который равен null (то есть по сути неопределен)
И при необходимости мы можем разграничить обработку различных типов исключений, включив дополнительные блоки catch:
В данном случае блоки catch обрабатывают исключения типов IndexOutOfRangeException и DivideByZeroException. Когда в блоке try возникнет исключение, то CLR будет искать нужный блок catch для обработки исключения. Так, в данном случае на строке
происходит обращение к 7-му элементу массива. Однако поскольку в массиве только 4 элемента, то мы получим исключение типа IndexOutOfRangeException. CLR найдет блок catch, который обрабатывает данное исключение, и передаст ему управление.
Следует отметить, что в данном случае в блоке try есть ситуация для генерации второго исключения — деление на ноль. Однако поскольку после генерации IndexOutOfRangeException управление переходит в соответствующий блок catch, то деление на ноль int y = x / 0 в принципе не будет выполняться, поэтому исключение типа DivideByZeroException никогда не будет сгенерировано.
Однако рассмотрим другую ситуацию:
В данном случае в блоке try генерируется исключение типа InvalidCastException, однако соответствующего блока catch для обработки данного исключения нет. Поэтому программа аварийно завершит свое выполнение.
Мы также можем определить для InvalidCastException свой блок catch, однако суть в том, что теоретически в коде могут быть сгенерированы самыи разные типы исключений. А определять для всех типов исключений блоки catch, если обработка исключений однотипна, не имеет смысла. И в этом случае мы можем определить блок catch для базового типа Exception:
И в данном случае блок catch (Exception ex)<> будет обрабатывать все исключения кроме DivideByZeroException и IndexOutOfRangeException. При этом блоки catch для более общих, более базовых исключений следует помещать в конце — после блоков catch для более конкретный, специализированных типов. Так как CLR выбирает для обработки исключения первый блок catch, который соответствует типу сгенерированного исключения. Поэтому в данном случае сначала обрабатывается исключение DivideByZeroException и IndexOutOfRangeException, и только потом Exception (так как DivideByZeroException и IndexOutOfRangeException наследуется от класса Exception).