5 Ошибки и исключения

advertisement
1
5 Ошибки и исключения
Исключительная ситуация, или просто исключение, происходит во время выполнения.
Используя подсистему обработки исключительных ситуаций в С#, можно обрабатывать структурированным и контролируемым образом ошибки, возникающие при выполнении программы. Главное преимущество обработки исключительных ситуаций заключается в том, что
она позволяет автоматизировать получение большей части кода, который раньше приходилось
вводить в любую крупную программу вручную для обработки ошибок. Так, если программа
написана на языке программирования без обработки исключительных ситуаций, то при неудачном выполнении методов приходится возвращать коды ошибок, которые необходимо проверять вручную при каждом вызове метода. Это не только трудоемкий, но и чреватый ошибками процесс. Обработка исключительных ситуаций рационализирует весь процесс обработки
ошибок, позволяя определить в программе блок кода, называемый обработчиком исклю чений
и выполняющийся автоматически, когда возникает ошибка. Это избавляет от необходимости
проверять вручную, насколько удачно или неудачно завершилась конкретная операция либо
вызов метода. Если возникнет ошибка, она будет обработана соответствующим образом обработчиком ошибок.
Обработка исключительных ситуаций важна еще и потому, что в С# определены стандартные исключения для типичных программных ошибок, например деление на нуль или выход индекса за границы массива. Для реагирования на подобные ошибки в программе должно
быть организовано отслеживание и обработка соответствующих исключительных ситуаций.
Ведь в конечном счете для успешного программирования на С# необходимо научиться умело
пользоваться подсистемой обработки исключительных ситуаций.
5.1 Класс System.Exception
В С# исключения представлены в виде классов. Все классы исключений должны быть
производными от встроенного в С# класса Exception, являющегося частью пространства
имен System. Следовательно, все исключения являются подклассами класса Exception.
К числу самых важных подклассов Exception относится класс SystemException.
Именно от этого класса являются производными все исключения, генерируемые исполняющей
системой С# (т.е. системой CLR). Класс SystemException ничего не добавляет к классу
Exception, а просто определяет вершину иерархии стандартных исключений.
В среде .NET Framework определено несколько встроенных исключений, являю щихся
производными от класса SystemException. Например, при попытке выполнить деление на
нуль генерируется исключение DivideByZeroException. Как будет показано далее в этой
главе, в С# можно создавать собственные классы исключений, производные от класса
Exception.
5.2 Основы обработки исключительных ситуаций
Обработка исключительных ситуаций в С# организуется с помощью четырех ключевых
слов: try, catch, throw и finally. Они образуют взаимосвязанную подсистему, в которой
применение одного из ключевых слов подразумевает применение другого. На протяжении
всей этой главы назначение и применение каждого из упомянутых выше ключевых слов будет
рассмотрено во всех подробностях. Но прежде необходимо дать общее представление о роли
каждого из них в обработке исключительных ситуаций. Поэтому ниже кратко описан принцип
их действия.
Операторы программы, которые требуется контролировать на появление исключений,
заключаются в блок try. Если внутри блока try возникает исключительная ситуация, генерируется исключение. Это исключение может быть перехвачено и обработано каким-нибудь рациональным способом в коде программы с помощью оператора, обозначаемого ключевым словом catch. Исключения, возникающие на уровне системы, генерируются исполняющей системой автоматически. А для генерирования исключений вручную служит ключевое слово throw.
2
Любой код, который должен быть непременно выполнен после выхода из блока try, помещается в блок finally.
5.2.1 Применение пары ключевых слов try и catch
Основу обработки исключительных ситуаций в С# составляет пара ключевых слов try
и catch. Эти ключевые слова действуют совместно и не могут быть использованы порознь.
Ниже приведена общая форма определения блоков try/catch для обработки исключительных ситуаций:
try
{
// Блок кода, проверяемый на наличие ошибок.
}
catch (ExcepType1 exOb)
{
// Обработчик исключения типа ExcepType1.
}
catch {ЕхсерТуре2 exOb)
{
// Обработчик исключения типа ЕхсерТуре2.
}
.
.
.
где ЕхсерТуре  это тип возникающей исключительной ситуации. Когда исключение генерируется оператором try, оно перехватывается составляющим ему пару оператором catch, ко-
торый затем обрабатывает это исключение. В зависимости от типа исключения выполняется и
соответствующий оператор catch. Так, если типы генерируемого исключения и того, что указывается в операторе catch, совпадают, то выполняется именно этот оператор, а все остальные пропускаются. Когда исключение перехватывается, переменная исключения exOb получает свое значение.
На самом деле указывать переменную exOb необязательно. Так, ее необязательно указывать, если обработчику исключений не требуется доступ к объекту исключения, что бывает
довольно часто. Для обработки исключения достаточно и его типа. Именно поэтому во многих
примерах программ, приведенных в этой главе, переменная exOb опускается.
Следует, однако, иметь в виду, что если исключение не генерируется, то блок оператора
try завершается как обычно, и все его операторы catch пропускаются. Выполнение программы возобновляется с первого оператора, следующего после завершающего оператора
catch. Таким образом, оператор catch выполняется лишь в том случае, если генерируется
исключение.
5.2.2 Простой пример обработки исключительной ситуации
Рассмотрим простой пример, демонстрирующий отслеживание и перехватывание исключения. Как вам должно быть уже известно, попытка индексировать массив за его границами приводит к ошибке. Когда возникает подобная ошибка, система CLR генерирует исключение IndexOutOfRangeException, которое определено как стандартное для среды .NET
Framework. В приведенной ниже программе такое исключение генерируется намеренно и затем перехватывается.
Листинг 5.1
// Продемонстрировать обработку исключительной ситуации.
using System;
class ExcDemo1
3
{
static void Main()
{
int[] nums = new int[4];
try {
Console.WriteLine("До генерирования исключения.");
// Сгенерировать исключение в связи с выходом индекса
// за границы массива.
for(int i=0; i < 10; i++) {
nums[i] = i;
Console.WriteLine("nums[{0}]: {1}", i, nums[i]);
}
Console.WriteLine("Не подлежит выводу");
}
catch (IndexOutOfRangeException) {
// Catch the exception.
Console.WriteLine("Индекс вышел за границы массива!");
}
Console.WriteLine("После блока перехвата исключения.");
}
}
При выполнении этой программы получается следующий результат.
До генерирования исключения.
nums[0]: 0
nums[l]: 1
nums[2]: 2
nums[3]: 3
Индекс вышел за границы массива!
После блока перехвата исключения.
В данном примере массив nums типа int состоит из четырех элементов. Но в цикле for
предпринимается попытка проиндексировать этот массив от 0 до 9, что и приво дит к появлению исключения IndexOutOfRangeException, когда происходит обращение к элементу массива по индексу 4.
Несмотря на всю свою краткость, приведенный выше пример наглядно демонстрирует
ряд основных моментов процесса обработки исключительных ситуаций. Во-первых, код, который требуется контролировать на наличие ошибок, содержится в блоке try. Во-вторых, когда возникает исключительная ситуация (в данном случае  при попытке проиндексировать
массив nums за его границами в цикле for), в блоке try генерируется исключение, которое
затем перехватывается в блоке catch. В этот момент выполнение кода в блоке try завершается и управление передается блоку catch. Это означает, что оператор catch не вызывается
специально, а выполнение кода переходит к нему автоматически. Следовательно, оператор,
содержащий метод WriteLine() и следующий непосредственно за циклом for, где происходит выход индекса за границы массива, вообще не выполняется. А в задачу обработчика исключений входит исправление ошибки, приведшей к исключительной ситуации, чтобы продолжить выполнение программы в нормальном режиме.
Обратите внимание на то, что в операторе catch указан только тип исключения (в данном случае  IndexOutOfRangeException ), а переменная исключения отсутствует. Как упоминалось ранее, переменную исключения требуется указывать лишь в том случае, если требуется доступ к объекту исключения. В ряде случаев значение объекта исключения может быть
использовано обработчиком исключений для получения дополнительной информации о самой
ошибке, но зачастую для обработки исключительной ситуации достаточно просто знать, что
4
она произошла. Поэтому переменная исключения нередко отсутствует в обработчиках исключений, как в рассматриваемом здесь примере.
Как пояснялось ранее, если исключение не генерируется в блоке try, то блок catch не
выполняется, а управление программой передается оператору, следующему после блока
catch. Для того чтобы убедиться в этом, замените в предыдущем примере программы строку
кода
for(int i=0;
i < 10;
i++) {
на строку
for(int i=0;
i < nums.Length;
i++) {
Теперь индексирование массива не выходит за его границы в цикле for. Следовательно,
никакого исключения не генерируется и блок catch не выполняется.
5.2.3 Второй пример обработки исключительной ситуации
Следует особо подчеркнуть, что весь кол выполняемый в блоке try, контролируется на
предмет исключительных ситуаций, в том числе и тех, которые могут возникнуть в результате
вызойа метода из самого блока try. Исключение, генерируемое методом в блоке try, может
быть перехвачено в том же блоке, если, конечно, этого не будет сделано в самом методе.
В качестве еще одного примера рассмотрим следующую программу, где блок try помещается в методе Main(). Из этого блока вызывается метод GenException(), в котором и
генерируется исключение IndexOutOfRangeException . Это исключение не перехватывается
методом GenException(). Но поскольку метод GenException() вызывается из блока try в
методе Main (), то исключение перехватывается в блоке catch, связанном непосредственно с
этим блоком try.
Листинг 5.2
/* Исключение может быть сгенерировано одним методом
и перехвачено другим. */
using System;
class ExcTest {
// Сгенерировать исключение.
public static void GenException()
{
int[] nums = new int[4];
Console.WriteLine("До генерирования исключения.");
// Сгенерировать исключение в связи с выходом индекса
// за границы массива.
for(int i=0; i < 10; i++) {
nums[i] = i;
Console.WriteLine("nums[{0}]: {1}", i, nums[i]);
}
Console.WriteLine("Не подлежит выводу");
}
}
class ExcDemo2
{
static void Main()
{
5
try
{
ExcTest.GenException();
}
catch (IndexOutOfRangeException)
{
// Перехватить исключение.
Console.WriteLine("Индекс вышел за границы массива!");
}
Console.WriteLine("После блока перехвата исключения.");
}
}
Выполнение этой программы дает такой же результат, как и в предыдущем примере.
До генерирования исключения.
nums[0]: 0
nums[1]: 1
nums[2]: 2
nums[3]: 3
Индекс вышел за границы массива!
После блока перехвата исключения.
Как пояснялось выше, метод GenException() вызывается из блока try, и поэтому генерируемое им исключение перехватывается не в нем, а в блоке catch внутри метода Main().
А если бы исключение перехватывалось в методе GenException(), оно не было бы вообще
передано обратно методу Main().
5.3 Последствия неперехвата исключений
Перехват одного из стандартных исключений, как в приведенных выше примерах, дает
еще одно преимущество: он исключает аварийное завершение программы. Как только исключение будет сгенерировано, оно должно быть перехвачено каким-то фрагментом кода в определенном месте программы. Вообще говоря, если исключение не перехватывается в программе, то оно будет перехвачено исполняющей системой. Но дело в том, что исполняющая
система выдаст сообщение об ошибке и прервет выполнение программы. Так, в приведенном
ниже примере программы исключение в связи с выходом индекса за границы массива не перехватывается.
Листинг 5.3
// Предоставить исполняющей системе C#
// возможность самой обрабатывать ошибки.
using System;
class NotHandled
{
static void Main()
{
int[] nums = new int[4];
Console.WriteLine("До генерирования исключения.");
// Сгенерировать исключение в связи с выходом
// индекса за границы массива.
for(int i=0; i < 10; i++)
{
nums[i] = i;
Console.WriteLine("nums[{0}]: {1}", i, nums[i]);
6
}
}
}
Когда возникает ошибка индексирования массива, выполнение программы прерывается
и выдается следующее сообщение об ошибке.
Необработанное исключение: System.IndexOutOfRangeException:
Индекс находился вне границ массива.
в NotHandled.Main() в <имя_файла>:строка 16
Это сообщение уведомляет об обнаружении в методе NotHandled.Main() необработанного исключения типа System.IndexOutOfRangeException , которое связано с выходом
индекса за границы массива.
Такие сообщения об ошибках полезны для отладки программы, но, по меньше мере,
нежелательны при ее использовании на практике! Именно поэтому так важно организовать
обработку исключительных ситуаций в самой программе.
Как упоминалось ранее, тип генерируемого исключения должен соответствовать типу,
указанному в операторе catch. В противном случае исключение не будет перехвачено. Например, в приведенной ниже программе предпринимается попытка перехватить ошибку нарушения границ массива в блоке catch, реагирующем на исключение DivideByZeroException,
связанное с делением на нуль и являющееся еще одним стандартным исключением. Когда индексирование
массива
выходит
за
его
границы,
генерируется
исключение
IndexOutOfRangeException, но оно не будет перехвачено блоком catch, что приведет к
аварийному завершению программы.
Листинг 5.4
// Не сработает!
using System;
class ExcTypeMismatch
{
static void Main()
{
int[] nums = new int[4];
try {
Console.WriteLine("До генерирования исключения.");
// Сгенерировать исключение в связи с выходом
// индекса за границы массива.
for(int i=0; i < 10; i++)
{
nums[i] = i;
Console.WriteLine("nums[{0}]: {1}", i, nums[i]);
}
Console.WriteLine("Не подлежит выводу");
}
/* Если перехват рассчитан на исключение DivideByZeroException,
То перехватить ошибку нарушения границ массива не удастся. */
catch (DivideByZeroException)
{
// Перехватить исключение.
Console.WriteLine("Индекс вышел за границы массива!");
}
Console.WriteLine("После блока перехвата исключения.");
7
}
}
Вот к какому результату приводит выполнение этой программы.
До генерирования исключения.
nums[0] : 0
nums[l] : 1
nums[2] : 2
nums[3] : 3
Необработанное исключение: System.IndexOutOfRangeException:
Индекс находился вне границ массива
в ExcTypeMismatch.Main() в <имя_файла>:строка 18
Как следует из приведенного выше результата, в блоке catch, реагирующем на исключение DivideByZeroException, не удалось перехватить исключение IndexOutOfRangeException.
5.4 Обработка исключительных ситуаций  "изящный"
способ устранения программных ошибок
Одно из главных преимуществ обработки исключительных ситуаций заключается в том,
что она позволяет вовремя отреагировать на ошибку в программе и затем продолжить ее выполнение. В качестве примера рассмотрим еще одну программу, в которой элементы одного
массива делятся на элементы другого. Если при этом происходит деление на нуль, то генерируется исключение DivideByZeroException. Обработка подобной исключительной ситуации заключается в том, что программа уведомляет об ошибке и затем продолжает свое выполнение. Таким образом, попытка деления на нуль не приведет к аварийному завершению программы из-за ошибки при ее выполнении. Вместо этого ошибка обрабатывается "изящно", не
прерывая выполнение программы.
Листинг 5.5
// Изящно обработать исключительную ситуацию
// и продолжить выполнение программы.
using System;
class ExcDemo3 {
static void Main() {
int[] numer = { 4, 8, 16, 32, 64, 128 };
int[] denom = { 2, 0, 4, 4, 0, 8 };
for(int i=0; i < numer.Length; i++) {
try {
Console.WriteLine(numer[i] + " / " +
denom[i] + " равно " +
numer[i]/denom[i]);
}
catch (DivideByZeroException) {
// Перехватить исключение.
Console.WriteLine("Делить на нуль нельзя!");
}
}
}
}
Ниже приведен результат выполнения этой программы.
4/2 равно 2
8
Делить на нуль нельзя!
16/4 равно 4
32/4 равно 8
Делить на нуль нельзя!
128 / 8 равно 16
Из данного примера следует еще один важный вывод: как только исключение обработано, оно удаляется из системы. Поэтому в приведенной выше программе проверка ошибок в
блоке try начинается снова на каждом шаге цикла for, при условии, что все предыдущие
исключительные ситуации были обработаны. Это позволяет обрабатывать в программе повторяющиеся ошибки.
5.5 Применение нескольких операторов catch
С одним оператором try можно связать несколько операторов catch. И на практике
это делается довольно часто. Но все операторы catch должны перехватывать исключения разного типа. В качестве примера ниже приведена программа, в которой перехватываются
ошибки выхода за границы массива и деления на нуль.
Листинг 5.6
// Использовать несколько операторов catch.
using System;
class ExcDemo4 {
static void Main() {
// Здесь массив numer длиннее массива denom.
int[] numer = { 4, 8, 16, 32, 64, 128, 256, 512 };
int[] denom = { 2, 0, 4, 4, 0, 8 };
for(int i=0; i < numer.Length; i++) {
try {
Console.WriteLine(numer[i] + " / " +
denom[i] + " равно " +
numer[i]/denom[i]);
}
catch (DivideByZeroException) {
Console.WriteLine("Делить на нуль нельзя!");
}
catch (IndexOutOfRangeException) {
Console.WriteLine("Подходящий элемент не найден.");
}
}
}
}
Вот к какому результату приводит выполнение этой программы.
4/2 равно 2
Делить на нуль нельзя!
16/4 равно 4
32/4 равно 8
Делить на нуль нельзя!
128 / 8 равно 16
Подходящий элемент не найден.
Подходящий элемент не найден.
Как следует из приведенного выше результата, каждый оператор catch реагирует
только на свой тип исключения.
Вообще говоря, операторы catch выполняются по порядку их следования в программе.
9
Но при этом выполняется только один блок catch, в котором тип исключения совпадает с
типом генерируемого исключения. А все остальные блоки catch пропускаются.
5.6 Перехват всех исключений
Время от времени возникает потребность в перехвате всех исключений независимо от
их типа. Для этой цели служит оператор catch, в котором тип и переменная исключения не
указываются. Ниже приведена общая форма такого оператора.
catch
{
// обработка исключений
}
С помощью такой формы создается "универсальный" обработчик всех исключений, перехватываемых в программе.
Ниже приведен пример такого "универсального" обработчика исключений. Обратите
внимание на то, что он перехватывает и обрабатывает оба исключения,
IndexOutOfRangeException и DivideByZeroException , генерируемых, в программе.
Листинг 5.7
// Использовать "универсальный" обработчик исключений.
using System;
class ExcDemo5
{
static void Main()
{
// Здесь массив numer длиннее массива denom.
int[] numer = { 4, 8, 16, 32, 64, 128, 256, 512 };
int[] denom = { 2, 0, 4, 4, 0, 8 };
for(int i=0; i < numer.Length; i++) {
try {
Console.WriteLine(numer[i] + " / " +
denom[i] + " равно " +
numer[i]/denom[i]);
}
catch //"Универсальный" перехват.
{
Console.WriteLine("Возникла исключительная ситуация.");
}
}
}
}
При выполнении этой программы получается следующий результат.
4/2 равно 2
Возникла исключительная
16/4 равно 4
32/4 равно 8
Возникла исключительная
128 / 8 равно 16
Возникла исключительная
Возникла исключительная
ситуация.
ситуация.
ситуация.
ситуация.
Применяя "универсальный" перехват, следует иметь в виду, что его блок должен располагаться последним по порядку среди всех блоков catch.
10
В подавляющем большинстве случаев "универсальный" обработчик исключений не
применяется. Как правило, исключения, которые могут быть сгенерированы в коде, обрабатываются по отдельности. Неправильное использование "универсального" обработчика может
привести к тому, что ошибки, перехватывавшиеся при тестировании программы, маскируются.
Кроме того, организовать надлежащую обработку всех исключительных ситуаций в одном обработчике не так-то просто. Иными словами, "универсальный" обработчик исключений может
оказаться пригодным лишь в особых случаях, например в инструментальном средстве анализа
кода во время выполнения.
5.7 Вложение блоков try
Один блок try может быть вложен в другой. Исключение, генерируемое во внутреннем
блоке try и не перехваченное в соответствующем блоке catch, передается во внешний блок
try. В качестве примера ниже приведена программа, в которой исключение
IndexOutOfRangeException перехватывается не во внутреннем, а во внешнем блоке try.
Листинг 5.8
// Использовать вложенный блок try.
using System;
class NestTrys
{
static void Main()
{
// Здесь массив numer длиннее массива denom.
int[] numer = { 4, 8, 16, 32, 64, 128, 256, 512 };
int[] denom = { 2, 0, 4, 4, 0, 8 };
try { // внешний блок try
for(int i=0; i < numer.Length; i++) {
try { // вложенный блок try
Console.WriteLine(numer[i] + " / " +
denom[i] + " равно " +
numer[i]/denom[i]);
}
catch (DivideByZeroException)
{
Console.WriteLine("Делить на ноль нельзя!");
}
}
}
catch (IndexOutOfRangeException) {
Console.WriteLine("Подходящий элемент не найден.");
Console.WriteLine("Неисправимая ошибка – программа прервана.");
}
}
}
Выполнение этой программы приводит к следующему результату.
4/2 равно 2
Делить на нуль нельзя!
16/4 равно 4
32/4 равно 8
Делить на нуль нельзя!
128 / 8 равно 16
Подходящий элемент не найден.
Неисправимая ошибка - программа прервана.
11
В данном примере исключение, обрабатываемое во внутреннем блоке try и связанное
с ошибкой из-за деления на нуль, не мешает дальнейшему выполнению программы. Но ошибка
нарушения границ массива, обнаруживаемая во внешнем блоке try, приводит к прерыванию
программы.
Безусловно, приведенный выше пример демонстрирует далеко не единственное основание для применения вложенных блоков try, тем не менее из него можно сделать важный общий вывод. Вложенные блоки try нередко применяются для обработки различных категорий
ошибок разными способами. В частности, одни ошибки считаются неисправимыми и не подлежат исправлению, а другие ошибки незначительны и могут быть обработаны немедленно.
Как правило, внешний блок try служит для обнаружениям обработки самых серьезных ошибок, а во внутренних блоках try обрабатываются менее серьезные ошибки. Кроме того, внешний блок try может стать "универсальным" для тех ошибок, которые не подлежат обработке
во внутреннем блоке.
5.8 Генерирование исключений вручную
В приведенных выше примерах перехватывались исключения, генерировавшиеся исполняющей системой автоматически. Но исключение может быть сгенерировано и вручную с
помощью оператора throw. Ниже приведена общая форма такого генерирования:
throw exceptOb;
где в качестве exceptOb должен быть обозначен объект класса исключений, производного от
класса Exception.
Ниже приведен пример программы, в которой демонстрируется применение оператора
throw для генерирования исключения DivideByZeroException.
Листинг 5.9
// Сгенерировать исключение вручную.
using System;
class ThrowDemo
{
static void Main()
{
try {
Console.WriteLine("До генерирования исключения.");
throw new DivideByZeroException();
}
catch (DivideByZeroException) {
Console.WriteLine("Исключение перехвачено.");
}
Console.WriteLine("После пары операторов try/catch.");
}
}
Вот к какому результату приводит выполнение этой программы.
До генерирования исключения.
Исключение перехвачено.
После пары операторов try/catch.
Обратите внимание на то, что исключение DivideByZeroException было сгенерировано с использованием ключевого слова new в операторе throw. Не следует забывать, что в
данном случае генерируется конкретный объект, а следовательно, он должен быть создан перед генерированием исключения. Это означает, что сгенерировать исключение только по его
12
типу нельзя. В данном примере для создания объекта DivideByZeroException был автоматически вызван конструктор, используемый по умолчанию, хотя для генерирования исключений доступны и другие конструкторы.
5.8.1 Повторное генерирование исключений
Исключение, перехваченное в одном блоке catch, может быть повторно сгенерировано
в другом блоке, чтобы быть перехваченным во внешнем блоке catch. Наиболее вероятной
причиной для повторного генерирования исключения служит предоставление доступа к исключению нескольким обработчикам. Допустим, что один обработчик оперирует каким-нибудь одним аспектом исключения, а другой обработчик  другим его аспектом. Для повторного генерирования исключения достаточно указать оператор throw без сопутствующего выражения, как в приведенной ниже форме.
throw ;
Не следует, однако, забывать, что когда исключение генерируется повторно, то оно не
перехватывается снова тем же самым блоком catch, а передается во внешний блок catch.
В приведенном ниже примере программы демонстрируется повторное генерирование
исключения. В данном случае генерируется исключение IndexOutOfRangeException.
Листинг 5.10
// Сгенерировать исключение повторно.
using System;
class Rethrow
{
public static void GenException()
{
// Здесь массив numer длиннее массива denom.
int[] numer = { 4, 8, 16, 32, 64, 128, 256, 512 };
int[] denom = { 2, 0, 4, 4, 0, 8 };
for(int i=0; i<numer.Length; i++)
{
try
{
Console.WriteLine(numer[i] + " / " +
denom[i] + " равно " +
numer[i]/denom[i]);
}
catch (DivideByZeroException)
{
Console.WriteLine("Делить на нуль нельзя!");
}
catch (IndexOutOfRangeException)
{
Console.WriteLine("Подходящий элемент не найден.");
throw; // сгенерировать исключение повторно
}
}
}
}
class RethrowDemo
{
static void Main()
{
13
try {
Rethrow.GenException();
}
catch(IndexOutOfRangeException)
{
// перехватить исключение повторно
Console.WriteLine("Неисправимая ошибка - " +
"программа прервана.");
}
}
}
В этом примере программы ошибки из-за деления на нуль обрабатываются локально в
методе GenException(), но ошибка выхода за границы массива генерируется повторно. В
данном случае исключение IndexOutOfRangeException обрабатывается в методе Main().
5.9 Использование блока finally
Иногда требуется определить кодовый блок, который будет выполняться после выхода
из блока try/catch. В частности, исключительная ситуация может возникнуть в связи с
ошибкой, приводящей к преждевременному возврату из текущего метода. Но в этом методе
мог быть открыт файл, который нужно закрыть, или же установлено сетевое соединение, требующее разрывания. Подобные ситуации нередки в программировании, и поэтому для их разрешения в С# предусмотрен удобный способ: воспользоваться блоком finally.
Для того чтобы указать кодовый блок, который должен выполняться после блока
try/catch, достаточно вставить блок finally в конце последовательности операторов
try/catch. Ниже приведена общая форма совместного использования блоков try/ catch и
finally.
try
{
// Блок кода, предназначенный для обработки ошибок.
}
catch (ExcepTypel exOb)
{
// Обработчик исключения типа ExcepType1.
}
catch (ЕхсерТуре2 exOb)
{
// Обработчик исключения типа ЕхсерТуре2.
.
.
.
}
finally {
// Код завершения обработки исключений.
}
Блок finally будет выполняться всякий раз, когда происходит выход из блока
try/catch, независимо от причин, которые к этому привели. Это означает, что если блок try
завершается нормально или по причине исключения, то последним выполняется код, определяемый в блоке finally. Блок finally выполняется и в том случае, если любой код в блоке
try или в связанных с ним блоках catch приводит к возврату из метода.
Ниже приведен пример применения блока finally.
Листинг 5.11
// Использовать блок finally.
14
using System;
class UseFinally
{
public static void GenException(int what)
{
int t;
int[] nums = new int[2];
Console.WriteLine("Получить " + what);
try {
switch(what) {
case 0:
t = 10 / what; // сгенерировать ошибку из-за деления на нуль
break;
case 1:
nums[4] = 4; // сгенерировать ошибку индексирования массива
break;
case 2:
return; // возврат из блока try
}
}
catch (DivideByZeroException) {
Console.WriteLine("Делить на нуль нельзя!");
return; // возврат из блока catch
}
catch (IndexOutOfRangeException) {
Console.WriteLine("Совпадающий элемент не найден.");
}
finally {
Console.WriteLine("После выхода из блока try.");
}
}
}
class FinallyDemo
{
static void Main()
{
for(int i=0; i < 3; i++) {
UseFinally.GenException(i);
Console.WriteLine();
}
}
}
Вот к какому результату приводит выполнение этой программы.
Получить 0
Делить на нуль нельзя
После выхода из блока try.
Получить 1
Совпадающий элемент не найден.
осле выхода из блока try.
Получить 2
После выхода из блока try.
15
Как следует из приведенного выше результата, блок finally выполняется независимо
от причины выхода из блока try.
И еще одно замечание: с точки зрения синтаксиса блок finally следует после блока
try, и формально блоки catch для этого не требуются. Следовательно, блок finally можно
ввести непосредственно после блока try, опустив блоки catch. В этом случае блок finally
начнет выполняться сразу же после выхода из блока try, но исключения обрабатываться не
будут.
5.10 Подробное рассмотрение класса Exception
В приведенных выше примерах исключения только перехватывались, но никакой существенной обработке они не подвергались. Как пояснялось выше, в операторе catch допускается указывать тип и переменную исключения. Переменная получает ссылку на объект исключения. Во всех исключениях поддерживаются члены, определенные в классе Exception, поскольку все исключения являются производными от этого класса. В этом разделе будет рассмотрен ряд наиболее полезных членов и конструкторов класса Exception и приведены конкретные примеры использования переменной исключения.
В классе Exception определяется ряд свойств. К числу самых интересных относятся три
свойства: Message, StackTrace и TargetSite. Все эти свойства доступны только для чтения. Свойство Message содержит символьную строку, описывающую характер ошибки; свойство StackTrace  строку с вызовами стека, приведшими к исключительной ситуации, а свойство TargetSite получает объект, обозначающий метод, сгенерировавший исключение.
Кроме того, в классе Exception определяется ряд методов. Чаще всего приходится
пользоваться методом ToString(), возвращающим символьную строку с описанием исключения. Этот метод автоматически вызывается, например, при отображении исключения с помощью метода WriteLine().
Применение всех трех упомянутых выше свойств и метода из класса Exception демонстрируется в приведенном ниже примере программы.
Листинг 5.12
// Использовать члены класса Exception.
using System;
class ExcTest
{
public static void GenException()
{
int[] nums = new int[4];
Console.WriteLine("До генерирования исключения.");
// Сгенерировать исключение в связи с выходом за границы массива.
for(int i=0; i < 10; i++) {
nums[i] = i;
Console.WriteLine("nums[{0}]: {1}", i, nums[i]);
}
Console.WriteLine("Не подлежит выводу");
}
}
class UseExcept
{
static void Main()
{
16
try {
ExcTest.GenException();
}
catch (IndexOutOfRangeException exc)
{
Console.WriteLine("Стандартное сообщение: ");
Console.WriteLine(exc); // вызвать метод ToString()
Console.WriteLine("Свойство StackTrace: " + exc.StackTrace);
Console.WriteLine("Свойство Message: " + exc.Message);
Console.WriteLine("Своймтво TargetSite: " + exc.TargetSite);
}
Console.WriteLine("После блока перехвата исключения.");
}
}
При выполнении этой программы получается следующий результат.
До генерирования исключения.
nums[0]: 0
nums[1]: 1
nums[2]: 2
nums[3]: 3
Стандартное сообщение таково: System.IndexOutOfRangeException: Индекс
находился вне границ массива.
в ExcTest.genException() в <имя_файла>:строка 15
в UseExcept.Main()в <имя_файла>:строка 29
Свойство StackTrace: в ExcTest.genException()в <имя_файла>:строка 15
в UseExcept.Main()B <имя_файла>:строка 29
Свойство Message: Индекс находился вне границ массива.
Свойство TargetSite: Void genException()
После блока перехвата исключения.
В классе Exception определяются четыре следующих конструктора.
public Exception ()
public Exception(string сообщение)
public Exception(string сообщение, Exception внутреннее_исключение)
protected Exception(System.Runtime.Serialization.SerializationInfо информация,
System.Runtime.Serialization.StreamingContext контекст)
Первый конструктор используется по умолчанию. Во втором конструкторе указывается
строка сообщение, связанная со свойством Message, которое имеет отношение к генерируемому исключению. В третьем конструкторе указывается так называемое внутреннее исключение. Этот конструктор используется в том случае, когда одно исключение порождает другое,
причем внутреннее исключение обозначает первое исключение, которое будет пустым, если
внутреннее исключение отсутствует. (Если внутреннее исключение присутствует, то оно может быть получено из свойства InnerException, определяемого в классе Exception.) И последний конструктор обрабатывает исключения, происходящие дистанционно, и поэтому требует десериали-зации.
Следует также заметить, что в четвертом конструкторе класса Exception типы
SerializationInfо
и
StreamingContext
относятся
к
пространству
имен
System.Runtime.Serialization.
5.10.1 Наиболее часто используемые исключения
В пространстве имен System определено несколько стандартных, встроенных исключений. Все эти исключения являются производными от класса SystemException, поскольку
они генерируются системой CLR при появлении ошибки во время выполнения. В табл. 13.1
перечислены некоторые наиболее часто используемые стандартные исключения.
17
Таблица 5.1 – Наиболее часто используемые исключения,
определенные в пространстве имен System
Исключение
Значение
ArrayTypeMismatchException
Тип сохраняемого значения несовместим с типом массива
DivideByZeroException
Попытка деления на нуль
IndexOutOfRangeException
Индекс оказался за границами массива
InvalidCastException
Неверно выполнено динамическое приведение типов
OutOfMemoryExceprion
Недостаточно свободной памяти для дальнейшего выполнения программы. Это исключение может быть, например,
сгенерировано, если для создания объекта с помощью оператора new не хватает памяти
OverflowException
Произошло арифметическое переполнение
NullReferenceException
Попытка использовать пустую ссылку, т. е. ссылку, которая
не указывает ни на один из объектов
Большинство исключений, приведенных в табл. 13.1, не требует особых пояснений,
кроме исключения NullReferenceException. Это исключение генерируется при попытке
использовать пустую ссылку на несуществующий объект, например, при вызове метода по пустой ссылке. Пустой называется такая ссылка, которая не указывает ни на один из объектов.
Для того чтобы создать такую ссылку, достаточно, например, присвоить явным образом пустое
значение переменной ссылочного типа, используя ключевое слово null. Пустые ссылки могут
также появляться и другими, менее очевидными путями. Ниже приведен пример программы,
демонстрирующий обработку исключения NullReferenceException.
Листинг 5.13
// Продемонстрировать обработку исключения NullReferenceException.
using System;
class X
{
int x;
public X(int a)
{
x = a;
}
public int Add(X o)
{
return x + o.x;
}
}
// Продемонстрировать генерирование и обработку
// исключения NullReferenceException.
class NREDemo
{
static void Main()
{
X p = new X(10);
X q = null; // присвоить явным образом пустое значение
// переменной q
int val;
18
try
{
val = p.Add(q); // эта операция приведет к исключительной
// ситуации
}
catch (NullReferenceException)
{
Console.WriteLine("Исключение NullReferenceException!");
Console.WriteLine("Исправление ошибки...\n");
// А теперь исправить ошибку.
q = new X(9);
val = p.Add(q);
}
Console.WriteLine("Значение val равно {0}", val);
}
}
Вот к какому результату приводит выполнение этой программы.
Исключение NullReferenceException!
Исправление ошибки...
Значение val равно 19
В приведенном выше примере программы создается класс X, в котором определяются
член х и метод Add(), складывающий значение члена х в вызывающем объекте со значением
члена х в объекте, передаваемом этому методу в качестве параметра. Оба объекта класса X
создаются в методе Main(). Первый из них (переменная р) инициализируется, а второй (переменная q)  нет. Вместо этого переменной q присваивается пустое значение. Затем вызывается
метод р.Add() с переменной q в качестве аргумента. Но поскольку переменная q не ссылается ни на один из объектов, то при попытке получить значение члена q. х генерируется исключение NullReferenceException.
5.11 Получение производных классов исключений
Несмотря на то что встроенные исключения охватывают наиболее распространенные
программные ошибки, обработка исключительных ситуаций в С# не ограничивается только
этими ошибками. В действительности одна из сильных сторон принятого в С# подхода к обработке исключительных ситуаций состоит в том, что в этом языке допускается использовать
исключения, определяемые пользователем, т.е. тем, кто программирует на С#. В частности,
такие специальные исключения можно использовать для обработки ошибок в собственном
коде, а создаются они очень просто. Для этого достаточно определить класс, производный от
класса Exception. В таких классах совсем не обязательно что-то реализовывать  одного
только их существования в системе типов уже достаточно, чтобы использовать их в качестве
исключений.
В прошлом специальные исключения создавались как производные от класса
Application.Exception, поскольку эта иерархия классов была первоначально зарезервирована для исключений прикладного характера. Но теперь корпорация Microsoft не рекомендует
этого делать, а вместо этого получать исключения, производные от класса Exception.
Создаваемые пользователем классы будут автоматически получать свойства и методы,
определенные в классе Exception и доступные для них. Разумеется, любой из этих членов
класса Exception можно переопределить в создаваемых классах исключений.
Когда создается собственный класс исключений, то, как правило, желательно, что бы в
19
нем поддерживались все конструкторы, определенные в классе Exception. В простых специальных классах исключений этого нетрудно добиться, поскольку для этого достаточно передать подходящие аргументы соответствующему конструктору класса Exception, используя
ключевое слово base. Но формально нужно предоставить только те конструкторы, которые
фактически используются в программе.
Рассмотрим пример программы, в которой используется исключение специального
типа. Напомним, что в конце главы 10 был разработан класс RangeArray, поддерживающий
одномерные массивы, в которых начальный и конечный индексы определяются пользователем. Так, например, вполне допустимым считается массив, индексируемый в пределах от -5 до
27. Если же индекс выходил за границы массива, то для обработки этой ошибки в классе
RangeArray была определена специальная переменная. Такая переменная устанавливалась и
проверялась после каждой операции обращения к массиву в коде, использовавшем класс
RangeArray. Безусловно, такой подход к обработке ошибок "неуклюж" и чреват дополнительными ошибками. В приведенном ниже улучшенном варианте класса RangeArray обработка
ошибок нарушения границ массива выполняется более изящным и надежным способом с помощью специально генерируемого исключения.
Листинг 5.14
// Использовать специальное исключение для обработки
// ошибок при обращении к массиву класса RangeArray.
using System;
// Создать исключение для класса RangeArray.
class RangeArrayException : Exception
{
/* Реализовать все конструкторы класса Exception. Такие
Конструкторы просто реализуют конструктор базового класса.
А поскольку класс исключения RangeArrayException ничего
не добавляет к классу Exception, то никаких дополнительных
действий не требуется */
public RangeArrayException() : base() { }
public RangeArrayException(string message) : base(message) { }
public RangeArrayException(string message,
Exception innerException) :
base(message, innerException) { }
protected RangeArrayException
(
System.Runtime.Serialization.SerializationInfo info,
System.Runtime.Serialization.StreamingContext context):
base(info, context) { }
// Переопределить метод ToString()
// для класса исключения RangeArrayException.
public override string ToString()
{
return Message;
}
}
// Улучшенный вариант класса RangeArray.
class RangeArray {
// Private data.
int[] a; // ссылка на базовый массив
int lowerBound; // наименьший индекс
int upperBound; // наибольший индекс
20
// Автоматически реализуемое и доступное только для чтения
// свойство Length.
public int Length { get; private set; }
// Построить массив по заданному размеру.
public RangeArray(int low, int high)
{
high++;
if(high <= low) {
throw new RangeArrayException("Нижний индекс не меньше верхнего.");
}
a = new int[high - low];
Length = high - low;
lowerBound = low;
upperBound = --high;
}
// Этот индексатор для класса RangeArray.
public int this[int index] {
// Это аксессор get.
get {
if(ok(index)) {
return a[index - lowerBound];
} else {
throw new RangeArrayException("Ошибка нарушения границ.");
}
}
// Это аксессор set.
set {
if(ok(index)) {
a[index - lowerBound] = value;
}
else throw new RangeArrayException("Ошибка нарушения границ.");
}
}
// Возвратить логическое значение true, если
// индекс находится в установленных границах.
private bool ok(int index)
{
if(index >= lowerBound & index <= upperBound) return true;
return false;
}
}
// Продемонстрировать применение массива с произвольно
// задаваемыми пределами индексирования.
class RangeArrayDemo
{
static void Main()
{
try {
RangeArray ra = new RangeArray(-5, 5);
RangeArray ra2 = new RangeArray(1, 10);
// Использовать объект ra в качестве массива.
Console.WriteLine("Длина массива ra: " + ra.Length);
21
for(int i = -5; i <= 5; i++)
ra[i] = i;
Console.Write("Содержимое массива ra: ");
for(int i = -5; i <= 5; i++)
Console.Write(ra[i] + " ");
Console.WriteLine("\n");
// Использовать объект ra2 в качестве массива.
Console.WriteLine("Длина массива ra2: " + ra2.Length);
for(int i = 1; i <= 10; i++)
ra2[i] = i;
Console.Write("Содержимое массива ra2: ");
for(int i = 1; i <= 10; i++)
Console.Write(ra2[i] + " ");
Console.WriteLine("\n");
} catch (RangeArrayException exc) {
Console.WriteLine(exc);
}
// А теперь продемонстрировать обработку некоторых ошибок.
Console.WriteLine("Сгенерировать ошибки нарушения границ.");
// Использовать неверно заданный индекс.
try {
RangeArray ra3 = new RangeArray(100, -10); // Error
} catch (RangeArrayException exc) {
Console.WriteLine(exc);
}
// Use an invalid index.
try
{
RangeArray ra3 = new RangeArray(-2, 2);
for(int i = -2; i <= 2; i++)
ra3[i] = i;
Console.Write("Содержимое массива ra3: ");
for(int i = -2; i <= 10; i++) // Сгенерировать ошибку
// нарушения границ
Console.Write(ra3[i] + " ");
} catch (RangeArrayException exc) {
Console.WriteLine(exc);
}
}
}
После выполнения этой программы получается следующий результат.
Длина массива rа: 11
Содержимое массива rа: -5-4-3-2-1 0 1 2 3 4 5
22
Длина массива rа2: 10
Содержимое массива ra2:
1 2 3 4 5 6 7 8 9 10
Сгенерировать ошибки нарушения границ.
Нижний индекс не меньше верхнего.
Содержимое массива rа3: -2-1012 Ошибка нарушения границ.
Когда возникает ошибка нарушения границ массива класса RangeArray, генерируется
объект типа RangeArrayException. В классе RangeArray это может произойти в трех следующих местах: в аксессоре get индексатора, в аксессоре set индексатора и в конструкторе
класса RangeArray. Для перехвата этих исключений подразумевается, что объекты типа
RangeArray должны быть сконструированы и доступны из блока try, что и продемонстрировано в приведенной выше программе. Используя специальное исключение для сообщения об
ошибках, класс RangeArray теперь действует как один из встроенных в С# типов данных, и
поэтому он может быть полностью интегрирован в механизм обработки ошибок, обнаруживаемых в программе.
Обратите внимание на то, что в теле конструкторов класса исключения
RangeArrayException отсутствуют какие-либо операторы, но вместо этого они просто передают свои аргументы классу Exception, используя ключевое слово base. Как пояснялось
ранее, в тех случаях, когда производный класс исключений не дополняет функции базового
класса, весь процесс создания исключений можно поручить конструкторам класса Exception.
Ведь производный класс исключений совсем не обязательно должен чем-то дополнять функции, наследуемые от класса Exception.
Прежде чем переходить к дальнейшему чтению, попробуйте немного поэксперимен тировать с приведенной выше программой. В частности, попробуйте закомментировать переопределение метода ToString() и понаблюдайте за результатами. Кроме того, попытайтесь
создать исключение, используя конструктор, вызываемый по умолчанию, и посмотрите, какое
сообщение при этом сформируется стандартными средствами С#.
5.12 Перехват исключений производных классов
При попытке перехватить типы исключений, относящихся как к базовым, так и к производным классам, следует особенно внимательно соблюдать порядок следования операторов
catch, поскольку перехват исключения базового класса будет совпадать с перехватом исключений любых его производных классов. Например, класс Exception является базовым для
всех исключений, и поэтому вместе с исключением типа Exception могут быть перехвачены
и все остальные исключения производных от него классов. Конечно, для более четкого перехвата всех исключений можно воспользоваться упоминавшейся ранее формой оператора
catch без указания конкретного типа исключения. Но вопрос перехвата исключений производных классов становится весьма актуальным и в других ситуациях, особенно при создании
собственных исключений.
Если требуется перехватывать исключения базового и производного классов, то первым
по порядку должен следовать оператор catch, перехватывающий исключение производного
класса. Это правило необходимо соблюдать потому, что при перехвате исключения базового
класса будут также перехвачены исключения всех производных от него классов. Правда, это
правило соблюдается автоматически: если первым расположить в коде оператор catch, перехватывающий исключение базового класса, то во время компиляции этого кода будет выдано
сообщение об ошибке.
В приведенном ниже примере программы создаются два класса исключений: ExceptA
и ExceptB. Класс ExceptA является производным от класса Exception, а класс ExceptB  производным от класса ExceptA. Затем в программе генерируются исключения каждого типа.
Ради краткости в классах специальных исключений предоставляется только один конструктор,
принимающий символьную строку, описывающую исключение. Но при разработке программ
23
коммерческого назначения в классах специальных исключений обычно требуется предоставлять все четыре конструктора, определяемых в классе Exception.
Листинг 5.15
// Исключения производных классов должны появляться до
// исключения базового класса.
using System;
// Создать класс исключения.
class ExceptA : Exception
{
public ExceptA(string message) : base(message) { }
public override string ToString()
{
return Message;
}
}
// Создать класс исключения, производный от класса ExceptA
class ExceptB : ExceptA
{
public ExceptB(string message) : base(message) { }
public override string ToString() {
return Message;
}
}
class OrderMatters
{
static void Main()
{
for(int x = 0; x < 3; x++) {
try {
if(x==0) throw new ExceptA("Перехват исключения типа ExceptA");
else if(x==1) throw new ExceptB("Перехват исключения типа ExceptB");
else throw new Exception();
}
catch (ExceptB exc) {
Console.WriteLine(exc);
}
catch (ExceptA exc) {
Console.WriteLine(exc);
}
catch (Exception exc) {
Console.WriteLine(exc);
}
}
}
}
Вот к какому результату приводит выполнение этой программы.
Перехват исключения типа ExceptA.
Перехват исключения типа ExceptB.
System.Exception:
Выдано исключение типа "System.Exception".
в OrderMatters.Main() в <имя_файла>:строка 36
24
Обратите внимание на порядок следования операторов catch. Именно в таком порядке
они и должны выполняться. Класс ExceptB является производным от класса ExceptA, поэтому исключение типа ExceptB должно перехватываться до исключения типа ExceptA. Аналогично, исключение типа Exception (т.е. базового класса для всех исключений) должно перехватываться последним. Для того чтобы убедиться в этом, измените порядок следования
операторов catch. В итоге это приведет к ошибке во время компиляции.
Полезным примером использования оператора catch, перехватывающего исключения
базового класса, служит перехват всей категории исключений. Допустим, что создается ряд
исключений для управления некоторым устройством. Если сделать их классы производными
от общего базового класса, то в тех приложениях, где необязательно выяснять конкретную
причину возникшей ошибки, достаточно перехватывать исключение базового класса и тем самым исключить ненужное дублирование кода.
5.13 Применение ключевых слов checked и unchecked
В С# имеется специальное средство, связанное с генерированием исключений, возникающих при переполнении в арифметических вычислениях. Как вам должно быть уже известно,
результаты некоторых видов арифметических вычислений могут превышать диапазон представления чисел для типа данных, используемого в вычислении. В этом случае происходит так
называемое переполнение. Рассмотрим в качестве примера следующий фрагмент кода.
byte а, b, result;
а = 127; b = 127;
result = (byte)(а * b);
В этом коде произведение значений переменных а и b превышает диапазон представления чисел для типа byte. Следовательно, результат вычисления данного выражения приводит
к переполнению для типа данных, сохраняемого в переменной result.
В С# допускается указывать, будет ли в коде сгенерировано исключение при переполнении, с помощью ключевых слов checked и unchecked. Так, если требуется указать, что
выражение будет проверяться на переполнение, следует использовать ключевое слово
checked, а если требуется проигнорировать переполнение  ключевое слово unchecked. В
последнем случае результат усекается, чтобы не выйти за пределы диапазона представления
чисел для целевого типа выражения.
У ключевого слова checked имеются две общие формы. В одной форме проверяется
конкретное выражение, и поэтому она называется операторной. А в другой форме проверяется
блок операторов, и поэтому она называется блочной. Ниже приведены обе формы:
checked (выражение)
checked
{
// проверяемые операторы
}
где выражение обозначает проверяемое выражение. Если вычисление проверяемого выражения приводит к переполнению, то генерируется исключение OverflowException.
У ключевого слова unchecked также имеются две общие формы. В первой, операторной форме переполнение игнорируется при вычислении конкретного выражения. А во второй,
блочной форме оно игнорируется при выполнении блока операторов:
unchecked (выражение)
unchecked
{
// операторы, для которых переполнение игнорируется
}
25
где выражение обозначает конкретное выражение, при вычислении которого переполнение
игнорируется. Если же в непроверяемом выражении происходит переполнение, то результат
его вычисления усекается.
Ниже приведен пример программы, в котором демонстрируется применение ключевых
слов checked и unchecked.
Листинг 5.16
// Продемонстрировать применение ключевых слов checked и unchecked.
using System;
class CheckedDemo
{
static void Main()
{
byte a, b;
byte result;
a = 127;
b = 127;
try {
result = unchecked((byte)(a * b));
Console.WriteLine("Непроверенный на переполнение результат: " +
result);
result = checked((byte)(a * b)); // эта операция приводит к
// исключительной ситуации
Console.WriteLine("Проверенный на переполнение результат: " +
result);
// не подлежит выполнению
}
catch (OverflowException exc)
{
Console.WriteLine(exc);
}
}
}
При выполнении этой программы получается следующий результат.
Непроверенный на переполнение результат: 1
System.OverflowException: Переполнение в результате
выполнения арифметической операции.
в CheckedDemo.Main() в <имя_файла>:строка 20
Как видите, результат вычисления непроверяемого выражения был усечен. А вычисление проверяемого выражения привело к исключительной ситуации.
В представленном выше примере программы было продемонстрировано применение
ключевых слов checked и unchecked в одном выражении. А в следующем примере программы показывается, каким образом проверяется и не проверяется на переполнение целый
блок операторов.
Листинг 5.17
// Продемонстрировать применение ключевых слов checked
// и unchecked в блоке операторов.
using System;
class CheckedBlocks
26
{
static void Main()
{
byte a, b;
byte result;
a = 127;
b = 127;
try
{
unchecked
{
a = 127;
b = 127;
result = unchecked((byte)(a * b));
Console.WriteLine("Непроверенный на переполнение результат: " +
result);
a = 125;
b = 5;
result = unchecked((byte)(a * b));
Console.WriteLine("Непроверенный на переполнение результат: " +
result);
}
checked
{
a = 2;
b = 7;
result = checked((byte)(a * b)); // правильно
Console.WriteLine("Проверенный на переполнение результат: " +
result);
a = 127;
b = 127;
result = checked((byte)(a * b)); // эта операция приводит к
// исключительной ситуации
Console.WriteLine("Проверенный на переполнение результат: " +
result); // не подлежит выполнению
}
}
catch (OverflowException exc)
{
Console.WriteLine(exc);
}
}
}
Результат выполнения этой программы приведен ниже.
Непроверенный на переполнение результат: 1
Непроверенный на переполнение результат: 113
Проверенный на переполнение результат: 14
System.OverflowException: Переполнение в результате
выполнения арифметической операции.
в CheckedDemo.Main() в <имя_файла>:строка 41
Как видите, результаты выполнения непроверяемого на переполнение блока операторов
27
были усечены. Когда же в проверяемом блоке операторов произошло переполнение, то возникла исключительная ситуация.
Потребность в применении ключевого слова checked или unchecked может возникнуть, в частности, потому, что по умолчанию проверяемое или непроверяемое состояние переполнения определяется путем установки соответствующего параметра компилятора и
настройки самой среды выполнения. Поэтому в некоторых программах состояние переполнения лучше проверять явным образом.
Download