Domnin_Lab_9-12 / ЛАБ_12_C# / ИСКЛЮЧИТЕЛЬНЫЕ СИТУАЦИИ И ИХ ОБРАБОТКА
.docИСКЛЮЧИТЕЛЬНЫЕ СИТУАЦИИ И ИХ ОБРАБОТКА (ИС)
ИС – это ошибки, вызванные пользователями или системой. Как правило они выявляются в период выполнения программы и требуют ее корректировки. Механизм обработки таких ошибок сейчас позполяет выявить (перехватить, установить) подобные ошибки, передать управление соответствующему обработчику ошибок, обработать ошибку, принять решение о режиме завершения обработки и только потом, если необходимо, создать сообщение для пользователя о результатах обработки и прервать выполнение задачи. Таким образом обработка ИС направлена на максимальное выполнение предваритеотных операций до прерывания ее выполнения. Особенностью С# является выделение и обработка стандартных ИС (наиболее часто встречаемых), для которых организовано их отслеживание и обработка.
В С# используется встроенный класс исключений 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");