Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Domnin_Lab_9-12 / ЛАБ_12_C# / ИСКЛЮЧИТЕЛЬНЫЕ СИТУАЦИИ И ИХ ОБРАБОТКА

.doc
Скачиваний:
16
Добавлен:
02.02.2015
Размер:
335.36 Кб
Скачать

ИСКЛЮЧИТЕЛЬНЫЕ СИТУАЦИИ И ИХ ОБРАБОТКА (ИС)

ИС – это ошибки, вызванные пользователями или системой. Как правило они выявляются в период выполнения программы и требуют ее корректировки. Механизм обработки таких ошибок сейчас позполяет выявить (перехватить, установить) подобные ошибки, передать управление соответствующему обработчику ошибок, обработать ошибку, принять решение о режиме завершения обработки и только потом, если необходимо, создать сообщение для пользователя о результатах обработки и прервать выполнение задачи. Таким образом обработка ИС направлена на максимальное выполнение предваритеотных операций до прерывания ее выполнения. Особенностью С# является выделение и обработка стандартных ИС (наиболее часто встречаемых), для которых организовано их отслеживание и обработка.

В С# используется встроенный класс исключений Exception, который является частью класса System. Из класса Exception выделены классы:

- SystemException для обработки исключений, которые генерируются С#-системой динамического управления или общеязыковым средством управления (CLR – Common Language Runtime). Из этого класса выделяется класс DivideByZeroEXception и т.д.;

- ApplicationException для обработки исключений, генерируемых прикладными програмами. Из этого класса можно создавать собственные классы обработки исключений.

Механизм обработки ИС в С# строится на использовании четырех ключевых слов: try, catch, throw, finally. Их основное назначение:

- try содержит программные инструкции для выделения возможных исключений и создает вызовы при их появлении;

- catch - программным путем перехватывает сообщения try-бл ока и обрабатывает его соответствующим образом;

- throw - обеспечивает генерирование исключений вручную (обычно системные исключения генерируются С#-системой динамического управления) ;

- finally содержит все коды, которые должны обязательно выполняться при выходе из try - блока.

Блоки try и catch всегда используются совместно. Их типовой формат

try {

// Проверяемый блок кода при наличие ошибок

}

catch ( ExcepType1 exOb ) {

// Обработчик для исключения типа ExcepType1.

}

catch ( ExcepType2 exOb ) {

// Обработчик для исключения типа ExcepType2.

}

где ExcepType тип сгенерированного исключения;

exOb - параметр значения перехваченного исключения, применяется только тогда, когда обработчику исключения нужен доступ к объекту исключения;

catch инструкция, перехватывающая соответствующее исключение и обрабатывающая его, таких инструкций может быть несколько или все (try -один).

Для примера рассмотрим две возможных структуры программы:

1 С одним классом – здесь находится все: Main(){ }, try { }, catch { }, т.е. структура текста программы такая

class Name1{ Main () { try { } catch() {} }}

2 C несколькими классами (например с двумя). C class Name2 блока try{F.E1()} cуществует переход в метод Maina(), но обратно возврата нет.

class Name1{ E1() { { } }}

class Name2{ Main() { try { F.E1()} catch() { } }}

Сравнение двух этих структур говорит о том, что при наличии обработки ИС они дают одинаковые результаты.

Сравнение подобных структур можно рекомендовать для начального пониматия технологии выполнения обработки исключительных ситуаций. В дальнейшем рассматриваются отдельные примеры обработки ИС.

1 Обработка неперехватываемых исключений

// Контролируется выход индекса массива за допустимые граници

// и catch() настроен на проверку выхода индекса за пределы.

using System;

class Name {

public static void Main() {

int[] k = new int[5];

try { Console.WriteLine("\n Создается индекс вне диапазона");

for(int i=0; i < 10; i++) {

k[i] = i;

Console.WriteLine(" k[{0}]: {1}", i, k[i]);

} // Создается ИС IndexOutOfRangeException

Console.WriteLine("Tекст не выводится");

}

// Console.WriteLine(" Эта строка не выводится");

catch (IndexOutOfRangeException ) { // Перехватываем исключене

Console.WriteLine(" Индекс вне границ");

}

Console.WriteLine(" После catch-инструкции \n\n\n ");

}

}

Рис 1.1 Пример обработки ИС при попадании индекса вне диапазона

// Контролируется выход индекса массива за допустимые граници,

// а catch() настроен на проверку DivideByZeroException.

using System;

class Name

{

public static void Main()

{ int[] k = new int[5];

try

{

Console.WriteLine("\n Создается индекс вне диапазона");

for (int i = 0; i < 10; i++)

{

k[i] = i;

Console.WriteLine(" k[{0}]: {1}", i, k[i]);

} // Создается ИС IndexOutOfRangeException

Console.WriteLine("Tекст не выводится");

}

catch (DivideByZeroException)

{ // Перехватываем исключене

Console.WriteLine(" Индекс вне границ");

}

Console.WriteLine(" После catch-инструкции \n\n\n ");

}

}

Рис 1.2 Обработка прерывания при несовпадении причины

с настройкой catch()

На приведенных результатах решения задачи № 1 показана обработка ИС (см. рис 1.1) и отсутствие обработки (см.рис 1.2), когда причина возникновения ИС ( IndexOutOfRangeException ) не совпадает с причиной настройки обработчика ( DivideByZeroException ) в функции catch().

2.1 Пример обработки исключений и продолжением работы пограммы

Пример выдачи сообщений об ошибках и продолжения работы программы. В сообщениях говорится о том, что делить на ноль нельзя.

// Пример продолжения работы программы при возникновении деления на 0

using System;

class Example1

{

public static void Main()

{

int[] numer = { 5, 10, -20, -45, 0 };

int[] denom = { 0, 10, 0, 30, 0 };

Console.WriteLine(" ");

for (int i = 0; i < numer.Length; i++ )

{

try

{

Console.WriteLine(" "+numer[i]+ "/"+denom[i]+" = "+numer[i]/denom[i]);

}

catch (DivideByZeroException)

{

// Перехватывание исключения

Console.WriteLine(" Делить на нуль нельзя!");

}

} Console.WriteLine("\n\n\n ");

}

}

Рис 2.1 Обработка ИС при деление на нуль с продолжением работы пограммы

№ 2.2

// Пример продолжения работы программы при возникновении деления на 0

using System;

class Example1

{

public static void Main()

{

double[] numer = { 5, 10, -20, -45, 0, 10.5, 20.99E+306};

double[] denom = { 0, 10, 0, 30, 0, 1.95, 0.116};

Console.WriteLine(" ");

for (int i = 0; i < 7; i++ )

{

try

{

Console.WriteLine(" "+numer[i]+ "/"+denom[i]+" = "+numer[i]/denom[i]);

}

catch (DivideByZeroException)

{ // Перехватывание исключения

Console.WriteLine(" Делить на нуль нельзя!");

}

} Console.WriteLine("\n\n\n ");

}

}

Рис 2.2 Пример обработки ИС при работе с double (предельными значениями)

Обработка ИС с несколькими catch-инструкциями

При этом все catch-инструкции должны обрабатывать различные прерывания.

№ 3

// Пример работы программы c различными catch-инструкциями

using System;

class Example1

{

public static void Main()

{

int[] numeri = { 5, 10, -20, -45, 0, 10, 20, 8, 55 };

int[] denomi = { 0, 10, 0, 30, 0, 15, 06 };

double[] numerd = { 5.0, 10.0, -20.0, -45.0, 0.0, 10.0, 20.9E+306, 8.0, 55.0 };

double[] denomd = { 0.0, 10.0, 0.0, 30.0, 0.0, 15.0, 0.1 };

Console.WriteLine(" ");

for (int i = 0; i < 9; i++)

{

try

{

Console.WriteLine(" " + numeri[i] + "/" + denomi[i] + " = " + numeri[i] / denomi[i]);

}

catch (DivideByZeroException)

{ // Перехватывание исключения

Console.WriteLine(" Делить на нуль нельзя!_i");

}

catch (IndexOutOfRangeException)

{ // Перехватывание исключения

Console.WriteLine(" Нет соответствующего элемента_i");

}

catch

{ // Перехватывание исключения

Console.WriteLine(" Произошло какое-то исключение_i");

}

}

Console.WriteLine("\n");

for (int i = 0; i < 9; i++)

{

try

{

Console.WriteLine(" "+numerd[i]+"/"+denomd[i]+" = "+numerd[i]/denomd[i]);

}

catch (DivideByZeroException)

{ // Перехватывание исключения

Console.WriteLine("\n Делить на нуль нельзя!_d");

}

catch (IndexOutOfRangeException)

{ // Перехватывание исключения

Console.WriteLine(" Нет соответствующего элемента_d");

}

catch

{ // Перехватывание исключения

Console.WriteLine(" Произошло какое-то исключение_d");

}

} Console.WriteLine("\n\n\n ");

}

}

Рис 3 Организация применения нескольких различных catch-инструкций.

Выводы:

1 try-блоки допучкают произвольное размещение.

2 try-блок может иметь несколько catch-блоков, но не менее одного.

3 Целевые catch-блоки ( catch() { } ) могут размещаться в try-блоке в любом порядке и только catch-блок общего назначения ( catch { } ) должен располагаться последним, в конце других catch –блоков, т.к. он способен перехватывать все прерывания. В приведенном примере все прерывания перехватывают специализированные catch-блоки.

Ниже приведен пример работы с вложенными try-блоками. Структура текста программы имеет вид :

class Name { Main() { try { for() { try {} catch() {} } } catch() {} } }

Как видно внутренние try- и catch- блоки распологаются внутри оператора цикла, а наружные try- и catch – блоки внутри функции Main(). Возможно формат структуры нагляднее размещать вертикально в виде:

class Name {

Main() {

try {

for () {

try {

….……….

}

catch() {

……..

}

}

catch() {

………

}

}

}

}

4

// Пример работы программы c различными try- и catch-инструкциями

using System;

class Example1

{ public static void Main()

{

int[] numeri = { 5, 10, -20, -45, 0, 10, 20, 8, 55 };

int[] denomi = { 0, 10, 0, 30, 0, 15, 06 };

double[] numerd = {5.0, 10.0,-20.0,-45.0, 0.0, 10.0, 20.9E+306, 8.0, 55.0 };

double[] denomd = {0.0, 10.0, 0.0, 30.0, 0.0, 15.0, 0.1 };

Console.WriteLine(" ");

try

{ // Внешний try-блок

for (int i = 0; i < 9; i++)

{

try

{ // Вложенный try- блок

Console.WriteLine(" " + numeri[i] + "/" + denomi[i] + " = " + numeri[i] / denomi[i]);

}

catch (DivideByZeroException)

{ // Перехватывание исключения

Console.WriteLine(" Делить на нуль нельзя!_i");

}

}

}

catch (IndexOutOfRangeException)

{ // Перехватывание исключения

Console.WriteLine(" Нет соответствующего элемента_i\n ");

}

catch

{ // Перехватывание исключения

Console.WriteLine(" Произошло какое-то исключение_i");

}

for (int i = 0; i < 9; i++)

{

try

{

Console.WriteLine(" " + numerd[i] + "/" + denomd[i] + " = " + numerd[i] / denomd[i]);

}

catch (DivideByZeroException)

{ // Перехватывание исключения

Console.WriteLine("\n Делить на нуль нельзя!_d");

}

catch (IndexOutOfRangeException)

{ // Перехватывание исключения

Console.WriteLine(" Нет соответствующего элемента_d");

}

catch

{ // Перехватывание исключения

Console.WriteLine(" Произошло какое-то исключение_d");

}

} Console.WriteLine("\n\n\n ");

}

}

Рис 4 Работа с вложенными try-блоками

Ручное генерирование исключений

Как правило возникновение ИС является непредвиденным событием, с которым работать всегда сложнее. Поэтому возможность создать ИС искусственно (самостоятельно) позволяет выработать заранее средства устранения его влияние. В классе System.Exception для создания ИС вручную используется инструкция throw. Ее формат такой

throw exceptOb;

где: exceptOb - объект класса исключений, производный от класса исключений Exception.

Наиболее посто создавать, когда это необходимо одно из известных исключени. Примером вызова исключения типа IndexOutOfRangeException служит текст следующей строки :

throw new IndexOutOfRangeException();

Обратите внимание на использование команды new и на название имени функции (использовано название прерывания, включенное в пространство имен System) Ниже показан текст программы перехвата вызванных искусственно двух ИС: выход за границы массива (1) и наличие арифметического переполнения (2). При этом ИС 1 перехватывается целевым catch-блоком, а ИС 2 (арифметическое переполнение) обнаруживается catch-блоком перехвата любого не обнаружунного ИС.

5

// Создание ручного прерывания

using System;

class Hand_madethrow {

public static void Main() {

try {

Console.WriteLine("\n До генерировния исключения_1");

throw new IndexOutOfRangeException(); //OverflowException();//

}

catch (IndexOutOfRangeException) {

Console.WriteLine(" Перехвачено ИС перехвачено_1 ");

}

try {

Console.WriteLine("\n До генерировния исключения_2 ");

throw new OverflowException();//IndexOutOfRangeException();

}

catch {

Console.WriteLine(" Какое-то ИС перехвачено_2 ");

}

Console.WriteLine(" Завершение try/catch блока\n\n\n ");

}

}

Рис 5 Перехват вызванных вручную ИС

Рекомендуем выполнить эту программу при разном взаимном размещении текстов программ try- и catch-блоков.

Повторное генерирование ИС

Одно ИС может возникать несколько раз, а catch-блок после одного перехвата ИС передает управление другому catch-блоку. Для повторного генерирования того же ИС достаточно использовать инструкцию вызова ИC в виде throw ; , т.е. не указывая имени. Структура текста программы имет вид:

class N1{ Excep() { for(i) { try { } catch(1) { } catch(2) { throw; } } } }

class N2 { Main() { try { N1.Excep() } catch(2) { } } }

Как видно ИС-2 вызывается дважды: в классе N1 и второй раз в классе N2. В классе N1 в функции Excep() организован выход за пределы индекса i, создание исключительной ситуации и ее обработки catch блоком и искуственный вызов второй раз этой же ситуации (throw ;). В классе N2 в функции Main() обращаются к функции Excep() класса N1 и после возврата в Main() обрабатывают catch(2)-блоком искусственно вызванное второй раз тоже самое прерывание. Текст програмы приведен ниже.

№ 6

// Повторная генерация исключения - throw;

using System;

class Reiter

{

public static void Excep() {

int[] numer = { 10, 20, 0, 100, 40, 70, 50, 4 };

int[] denom = { 2, 4, 0, 8, 10, 0 };

Console.WriteLine(" ");

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 Reiterrep {

public static void Main() {

try

{

Reiter.Excep();

}

catch (IndexOutOfRangeException)

{

// Перехватывается поворно сгенерированное исключение.

Console.WriteLine(" Неисправимая ошибка -- " + "программа закончена\n\n\n ");

}

}

}

Рис 6 Результаты повторного генерирования исключения

Применение блока finally для завершения обработки прерывания

Блок finally размещается при выходе из блока try/catch, что позволяет выполнить необходитые дополнительные операции по завершению обработки прерывания, связанных с особенностями внешних ситуаций (необходимостью закрыть окрытый файл, перекрыть связь с сетью и другие дополнительные обстоятельства). Формат использования этого блока следующий:

try {

// Блок обработки ошибок

}

catch(Excep_1 exOb) {

// Обработчик исключения типа Excep_1

}

………………….

catch(Excep_i exOb) {

// Обработчик исключения типа Excep_i

}

………………….

finally {

// Завершение обработки исключений

}

Обратите внимание, блок finally будет выполняться всегда. Это значит, что если перед finally размещается “к” catch-блоков, то блок finally будет выполняться “к” – раз.

Использование свойств и методов исключения

Здесь будет уделено внимание самим объектам исключения. Рассматриваются наиболее полезные члены и конструкторы класса Exception и особенность использования параметра catch-инструкции. В классе Exception определен ряд свойств и методов. Наиболее распространены свойства и методы:

- Message – содержит строку описания причины ошибки;

- StackTraseсодержит строку со стеком вызовов, вызвавших исключения;

-TargetSiteвызывает объект, задающий метод генерации исключения;

-ToString() – метод, возвращающий строку с описанием исключения.

Класс Exception использует четыре конструктора. Чаще применяются такие:

Exception() – конструктор по умолчанию;

Exception(string str) – принимает значения свойства Message, связанное с исключением.

Ниже приведена программа с использованием членов класса Exception.

7

// Работа с членами класса Exception

using System;

class Test {

public static void Excep()

{ int[] nums = new int[5];

Console.WriteLine("\n Перед генерированием исключения.");

// Контроль попадания индекса вне диапазона

for (int i = 0; i < 7; i++)

{

nums[i] = i; // строка текста пограммы - 12

Console.WriteLine(" nums[{0}]: {1}", i, nums[i]);

}

}

}

class UseExcep {

public static void Main() {

try {

Test.Excep(); // строка текста пограммы - 20

}

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(" После catch-инструкции.\n\n\n ");

}

}

Рис 7 Результаты использования членов класса Exception

Приведенные на рис 7 результаты позволяют сделать такие выводы.

1 Исключение возникло при i = 5.

2 Имело место исключение типа IndexOutOfRange – не соблюдение границ допустимого изменения индекса (в тексте программы 12 строка).

3 Исключение было сгенерировано методом Excep() (в тексте 20 строка).

Стандартные встроенные исключения генерируются системой динамического управления (Common Language Runtime) и все они включены в класс SystemException. Наиболее распространенные из них приведены в таблице 1

Таблица 1 Наиболее употребительные исключения

(в пространстве имен System)

Исключение

Значение

1

ArrayTypeMismatchException

Тип сохраняемого значения с типом массива несовместимы

2

DivideByZeroException

Попытка деления на нуль

3

IndexOutOfRangeException

Индекс массива оказался вне диапазона

4

InvalidCastException

Неверно выполнено динамическое приведение типов

5

OutOfMеmoryException

Обращение к оператору new неудачное из-за недостатка оперативной памяти

6

OverflowException

Имеет место арифметическое переполнение

7

NullReferenceException

Была сделана попытка использовать нулевую ссылку, которая не указывает ни на какой объект

8

StackOverflowException

Переполнение стека

Рассмотрим пример использования нулевой ссылки при выполнении оператора – тип имя_ссылки = null. В этом случае появляется исключение (7).

8

// Создание исключительной ситуации NullReferenceException

using System;

class X {

int x;

public X(int a) {

x = a;

}

public int add(X o) {

return x + o.x;

}

}

// Работа с исключением NullReferenceException

class NRE

{

public static void Main()

{

X p = new X(10);

X q = null; // q = null; явно

int val;

try

{

val = p.add(q); // Появится исключение

}

catch (NullReferenceException)

{

Console.WriteLine("\n NullReferenceException!");

Console.WriteLine(" Исправление ошибки \n");

// Исправление ошибки

q = new X(9);

val = p.add(q);

}

Console.WriteLine(" Значение val равно {0}\n\n\n", val);

}

}

Рис 8 Пример работы с исключением NullReferenceException

Наследование классов исключений

Здесь речь идет о обработке исключений, создаваемых пользователем (программистом). Эти исключения можно относить к производному классу от класса Exception. Отметим следующее:

1 Класс AplicationException зарезервирован как начало иерархии прикладных исключений пользователя.

2 Прикладные исключения пользователя должны быть производными от класса AplicationException.

3 Создаваемые программистом классы исключений будут автоматически иметь свойства и методы, определенные в классе Exception.

Ниже рассматривается пример, в котором при нарушении границ диапазона массива генерируется отдельное исключение. Текст программы специально подготовлен:

- пронумерованы строки (более 130 строк, номера поставлены на подходящих строках с комментариями);

- пронумерованны все операторы вывода, применена следующая структура нумерации - к ( <N|>N ) дополнителный текст. Здесь:

-к – порядковый оператор вывода по тексту программы,

- ( <N или >N) – ближайший имеющийся номер текста строки программы,

- дополнит. текст – для подчеркивания особенности оператора вывода.

Ниже приводится текст программы

9

// Cоздание пользовательского исключения

using System;

// Cоздаем исключение для класса RangeArray.

class RangeArrayException : ApplicationException {

// Реализуем стандартные конструкторы.

public RangeArrayException() : base() { }

public RangeArrayException(string str) : base(str) { }

// Переопределяем метод ToString() для класса RangeArrayException. 10

public override string ToString() { return Message; }

}

// Улучшенная версия класса RangeArray

class RangeArray {

// Закрытые данные.

int[] a; // Ccылка на базовый массив.

int lowerBound; // Наименьший индекс.

int upperBound; // Наибольший индекс. 20

int len; // Базовая переменная для свойства length.

// Создаем массив с заданным размером.

public RangeArray(int low, int high) {

high++;

if(high <= low) {

throw new RangeArrayException(" Нижний индекс не меньше верхнего -1 (>20)");

}

a = new int[high - low];

len = high - low;

lowerBound = low;

upperBound = --high;

}

// Cвойство Length, предназначенное только для чтения. 36

public int Length {

get {

return len;

}

}

// Индексатор для объектa класса RangeArray.

public int this[int index] {

// Средство для чтения элемента массива.

get {

if(ok(index)) {

return a[index - lowerBound];

} else {

throw new RangeArrayException("\n Ошибка нарушения границ диапазона -2 ( <54 ) в get ");

}

}

// Средство для записи элемента массива 54

set {

if(ok(index)) {

a[index - lowerBound] = value;

}

else throw new RangeArrayException (" Ошибка нарушения границ диапазона -3 ( >54 ) в set ");

}

}

// Метод возвращает значение true,

// если индекс в пределах диапазона.

private bool ok(int index) {

if(index >=lowerBound & index <= upperBound)

return true;

return false;

}

}

// Демонстрируем использование массива с заданным диапазоном изменения индекса 72

class RangeArrayDemo {

public static void Main() {

try {

RangeArray ra = new RangeArray(-5, 5);

RangeArray ra2 = new RangeArray(1, 10);

// Демонстрируем использование объекта массива ra.

Console.WriteLine("\n Длинна массива ra -4 ( >72 ) : "+ra.Length);

for(int i = -5; i <= 5; i++)

ra[i] = i;

Console.Write(" Содержимое массива ra -5 ( <90 ) : ");

for(int i = -5; i <= 5; i++)

Console.Write(ra[i] + " ");

Console.WriteLine("\n");