Полное руководство. С# 4.0
Шрифт:
ПРИМЕЧАНИЕ В прошлом специальные исключения создавались как производные от класса Application.Exception, поскольку эта иерархия классов была первоначально зарезер вирована для исключений прикладного характера. Но теперь корпорация Microsoft не реко мендует этого делать, а вместо этого получать исключения, производные от класса Exception. Именно по этой причине данный подход и рассматривается в настоящей книге.
Создаваемые пользователем классы будут автоматически получать свойства и мето ды, определенные в классе Exception и доступные для них. Разумеется, любой из этих членов класса Exception можно переопределить в создаваемых классах исключений. Когда создается собственный класс исключений, то, как правило, желательно, что бы в нем поддерживались все конструкторы, определенные в классе Exception. В про стых специальных классах исключений этого нетрудно добиться, поскольку для этого достаточно передать подходящие аргументы соответствующему конструктору класса Exception, используя ключевое слово base. Но формально нужно предоставить толь ко те конструкторы, которые фактически используются в программе.
Рассмотрим пример программы, в которой используется исключение специального типа. Напомним, что в конце главы 10 был разработан
// Использовать специальное исключение для обработки // ошибок при обращении к массиву класса RangeArray. using System;
// Создать исключение для класса RangeArray. class RangeArrayException : Exception { / Реализовать все конструкторы класса Exception. Такие конструкторы просто реализуют конструктор базового класса. А поскольку класс исключения RangeArrayException ничего не добавляет к классу Exception, то никаких дополнительных действий не требуется. / public RangeArrayException : base)) { } public RangeArrayException(string str) : base(str) { } public RangeArrayException( string str, Exception inner) : base (str, inner) { } protected RangeArrayException( System.Runtime.Serialization.SerializationInfo si, System.Runtime.Serialization.StreamingContext sc) : base(si, sc) { } // Переопределить метод ToString для класса исключения RangeArrayException. public override string ToString { return Message; }
}
// Улучшенный вариант класса RangeArray. class RangeArray { // Закрытые данные. int[] a; // ссылка на базовый массив int lowerBound; // наименьший индекс int upperBound; // наибольший индекс // Автоматически реализуемое и доступное только для чтения свойство Length. public int Length { get; private set; } // Построить массив по заданному размеру public RangeArray(int low, int high) { high++; if(high <= low) { throw new RangeArrayException("Нижний индекс не меньше верхнего."); } а = 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 S 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); 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); // Ошибка! } catch (RangeArrayException exc) { Console.WriteLine(exc); } // Использовать неверно заданный индекс. 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); } }
} После выполнения этой программы получается следующий результат.
Длина массива ra: 11 Содержимое массива ra: -5 -4 -3 -2 -1 0 1 2 3 4 5
Длина массива ra2: 10 Содержимое массива ra2: 1 2 3 4 5 6 7 8 9 10
Сгенерировать ошибки нарушения границ. Нижний индекс не меньше верхнего. Содержимое массива ra3: -2 -1 0 1 2 Ошибка нарушения границ. Когда возникает ошибка нарушения границ массива класса RangeArray, генери руется объект типа RangeArrayException. В классе RangeArray это может произой ти в трех следующих местах: в аксессоре get индексатора, в аксессоре set индексатора и в конструкторе класса RangeArray. Для перехвата этих исключений подразумева ется, что объекты типа RangeArray должны быть сконструированы и доступны из блока try, что и продемонстрировано в приведенной выше программе. Используя специальное исключение для сообщения об ошибках, класс RangeArray теперь дей ствует как один из встроенных в C# типов данных, и поэтому он может быть полностью интегрирован в механизм обработки ошибок, обнаруживаемых в программе. Обратите внимание на то, что в теле конструкторов класса исключения RangeArrayException отсутствуют какие-либо операторы, но вместо этого они про сто передают свои аргументы классу Exception, используя ключевое слово base. Как пояснялось ранее, в тех случаях, когда производный класс исключений не дополняет функции базового класса, весь процесс создания исключений можно поручить кон структорам класса Exception. Ведь производный класс исключений совсем не обяза тельно должен чем-то дополнять функции, наследуемые от класса Exception. Прежде чем переходить к дальнейшему чтению, попробуйте немного поэксперимен тировать с приведенной выше программой. В частности, попробуйте закомментировать переопределение метода ToString и понаблюдайте за результатами. Кроме того, по пытайтесь создать исключение, используя конструктор, вызываемый по умолчанию, и посмотрите, какое сообщение при этом сформируется стандартными средствами С#. ## Перехват исключений производных классов При попытке перехватить типы исключений, относящихся как к базовым, так и к производным классам, следует особенно внимательно соблюдать порядок следо вания операторов catch, поскольку перехват исключения базового класса будет со впадать с перехватом исключений любых его производных классов. Например, класс Exception является базовым для всех исключений, и поэтому вместе с исключением типа Exception могут быть перехвачены и все остальные исключения производных от него классов. Конечно, для более четкого перехвата всех исключений можно воспользо ваться упоминавшейся ранее формой оператора catch без указания конкретного типа исключения. Но вопрос перехвата исключений производных классов становится весьма актуальным и в других ситуациях, особенно при создании собственных исключений. Если требуется перехватывать исключения базового и производного классов, то первым по порядку должен следовать оператор catch, перехватывающий исключение производного класса. Это правило необходимо соблюдать потому, что при перехвате исключения базового класса будут также перехвачены исключения всех производных от него классов. Правда, это правило соблюдается автоматически: если первым рас положить в коде оператор catch, перехватывающий исключение базового класса, то во время компиляции этого кода будет выдано сообщение об ошибке. В приведенном ниже примере программы создаются два класса исключений: ExceptA и ExceptB. Класс ExceptA является производным от класса Exception, а класс ExceptB — производным от класса ExceptA. Затем в программе генерируются исключения каждого типа. Ради краткости в классах специальных исключений предо ставляется только один конструктор, принимающий символьную строку, описываю щую исключение. Но при разработке программ коммерческого назначения в классах специальных исключений обычно требуется предоставлять все четыре конструктора, определяемых в классе Exception.
// Исключения производных классов должны появляться до // исключений базового класса. using System;
// Создать класс исключения. class ExceptA : Exception { public ExceptA(string str) : base(str) { } public override string ToString { return Message; }
}
// Создать класс исключения, производный от класса ExceptA. class ExceptB : ExceptA { public ExceptB(string str) : base(str) { } 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 Обратите внимание на порядок следования операторов catch. Именно в таком по рядке они и должны выполняться. Класс ExceptB является производным от класса ExceptA, поэтому исключение типа ExceptB должно перехватываться до исключения типа ExceptA. Аналогично, исключение типа Exception (т.е. базового класса для всех исключений) должно перехватываться последним. Для того чтобы убедиться в этом, измените порядок следования операторов catch. В итоге это приведет к ошибке во время компиляции. Полезным примером использования оператора catch, перехватывающего исклю чения базового класса, служит перехват всей категории исключений. Допустим, что создается ряд исключений для управления некоторым устройством. Если сделать их классы производными от общего базового класса, то в тех приложениях, где необяза тельно выяснять конкретную причину возникшей ошибки, достаточно перехватывать исключение базового класса и тем самым исключить ненужное дублирование кода. ## Применение ключевых слов checked и unchecked В C# имеется специальное средство, связанное с генерированием исключений, воз никающих при переполнении в арифметических вычислениях. Как вам должно быть уже известно, результаты некоторых видов арифметических вычислений могут превы шать диапазон представления чисел для типа данных, используемого в вычислении. В этом случае происходит так называемое переполнение. Рассмотрим в качестве при мера следующий фрагмент кода.
byte a, b, result; а = 127; b = 127; result = (byte)(а * b); В этом коде произведение значений переменных а и b превышает диапазон пред ставления чисел для типа byte. Следовательно, результат вычисления данного вы ражения приводит к переполнению для типа данных, сохраняемого в переменной result. В C# допускается указывать, будет ли в коде сгенерировано исключение при пере полнении, с помощью ключевых слов checked и unchecked. Так, если требуется ука зать, что выражение будет проверяться на переполнение, следует использовать клю чевое слово checked, а если требуется проигнорировать переполнение — ключевое слово unchecked. В последнем случае результат усекается, чтобы не выйти за пределы диапазона представления чисел для целевого типа выражения. У ключевого слова checked имеются две общие формы. В одной форме проверя ется конкретное выражение, и поэтому она называется операторной. А в другой форме проверяется блок операторов, и поэтому она называется блочной. Ниже приведены обе формы:
checked (выражение)
checked { // проверяемые операторы } где выражение обозначает проверяемое выражение. Если вычисление прове ряемого выражения приводит к переполнению, то генерируется исключение OverflowException. У ключевого слова unchecked также имеются две общие формы. В первой, опера торной форме переполнение игнорируется при вычислении конкретного выражения. А во второй, блочной форме оно игнорируется при выполнении блока операторов:
unchecked (выражение)
unchecked { // операторы, для которых переполнение игнорируется } где выражение обозначает конкретное выражение, при вычислении которого перепол нение игнорируется. Если же в непроверяемом выражении происходит переполнение, то результат его вычисления усекается. Ниже приведен пример программы, в котором демонстрируется применение клю чевых слов checked и unchecked.