Шаг 224.
Язык программирования C#. Начала.
Обработка исключений. Вложенные конструкции try-catch и блок finally

    На этом шаге мы рассмотрим особенности использования указанных конструкций.

    Один try-блок (внешний) может содержать в себе try-catch конструкции (внутренние). Интерес представляет ситуация, когда ошибка происходит во внутреннем try-блоке. У этого внутреннего try-блока есть catch-блоки, предназначенные для обработки исключений. Возможны две ситуации:

    В первом случае все происходит штатно: ошибка обрабатывается, и после выполнения соответствующего catch-блока начинают выполняться команды после внутренней try-catch конструкции. А вот во втором случае, когда ошибка не обрабатывается во внутренней try-catch конструкции, в игру вступает внешняя try-catch конструкция. А именно, исключение, сгенерированное во внутреннем try-блоке, передается для обработки во внешнюю try-catch конструкцию. Если исключение обрабатывается в каком-то из catch-блоков этой конструкции, то дальше выполняются команды после внешней try-catch конструкции. Если ошибка не обрабатывается и во внешнем try-блоке, то исключение передается дальше, в следующую внешнюю try-catch конструкцию - конечно, если такая имеется. В конце концов, ошибка или будет обработана, или закончатся внешние try-catch конструкции. В последнем случае выполнение программы завершается и появляется сообщение об ошибке.

    Конструкцию try-catch, состоящую из try-блока и catch-блоков, можно дополнить еще и finally-блоком. Этот блок указывается после catch-блоков. То есть можно использовать конструкцию try-catch-finally такого вида (жирным шрифтом выделены ключевые элементы шаблона):

try {
  // Контролируемый код
}
catch(класс объект){
  // Команды для обработки исключения
}
finally {
  // Обязательные для выполнения команды
}

    В этой конструкции catch-блоков может быть несколько. Команды из блока finally выполняются в любом случае - и если ошибка в try-блоке возникла, и если ошибка в try-блоке не возникла. Возможны три принципиальные ситуации:

    Собственно, ради вот этого третьего сценария обычно и используют блок finally. Небольшой пример, в котором иллюстрируется использование вложенных try-catch конструкций и блока finally, представлен в примере ниже.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace pr224_1
{
    // Класс с главным методом:
    class Program
    {
        // Статический метод:
        static void generator(string num)
        {
            // Отображение сообщения:
            Console.WriteLine("Метод с аргументом \"{0}\" начинает paботу", num);
            // Текстовая переменная: 
            string msg = "Массив не создан!";
            // Контролируемый код: 
            try
            {
                // Преобразование текста в целое число: 
                intm = Int32.Parse(num);
                // Создание массива: 
                int[] array = new int[m];
                // Новое значение текстовой переменной: 
                msg = "Создан массив: ";
                // Цикл: 
                for (int k = 0; k <= 4; k++)
                {
                    // Элементу массива присваивается значение: 
                    array[k] = 12 / (4 - k);
                    // К тексту добавляется значение элемента: 
                    msg += " " + array[k];
                }
            }
            // Обработка ошибки выхода за пределы массива: 
            catch (IndexOutOfRangeException)
            {
                // Отображение сообщения:
                Console.WriteLine("Ошибка в методе: выход за пределы массива");
            }
            // Код для обязательного выполнения: 
            finally
            {
                // Отображение значения текстовой переменной:
                Console.WriteLine(msg);
            }
            // Отображение сообщения:
            Console.WriteLine("Meтод завершил работу");
        }

        // Главный метод: 
        static void Main()
        {
            // Текстовый массив:
            string[] args = { "один", "-2", "5", "2" };
            // Символьный массив из одного элемента: 
            char[] symbs = new char[1];
            // Цикл:
            for (int k = 0; k < args.Length; k++)
            {
                // Контролируемый код (внешний блок): 
                try
                {
                    // Отображение сообщения:
                    Console.WriteLine("Выполняется цикл №{0}: ", k + 1);
                    // Контролируемый код (внутренний блок):
                    try
                    {
                        // Вызов статического метода: 
                        generator(args[k]);
                        // Команда с ошибкой (неверный индекс): 
                        symbs[1] = 'A';
                    }
                    // Обработка ошибки деления на ноль 
                    // (внутренний блок): 
                    catch (DivideByZeroException e)
                    {
                        // Отображение сообщения:
                        Console.WriteLine("Первый внутренний catch-блок: {0}", e.GetType().Name);
                    }
                    // Обработка ошибки выхода за пределы массива 
                    // (внутренний блок): 
                    catch (IndexOutOfRangeException e)
                    {
                        // Отображение сообщения:
                        Console.WriteLine("Второй внутренний catch-блок: {0}", e.GetType().Name);
                    }
                    // Обязательный для выполнения код: 
                    finally
                    {
                        // Элементу массива присваивается значение: 
                        symbs[0] = (char)('A' + k);
                        // Отображение сообщения:
                        Console.WriteLine("Символ: \'{0}\'", symbs[0]);
                    }
                }
                // Обработка всех ошибок (внешний блок): 
                catch (Exception e)
                {
                    // Отображение сообщения:
                    Console.WriteLine("Внешний catch-блок: {0}", e.GetType().Name);
                }
                // Отображение сообщения:
                Console.WriteLine("Цикл №{0} завершен...\n", k + 1);
            }
            // Задержка:
            Console.ReadLine();
        }
    }
}
Архив проекта можно взять здесь.

    Результат выполнения программы может быть таким:


Рис.1. Результат выполнения программы

    В программе, в классе с главным методом описан статический метод generator(). Метод не возвращает результат и имеет текстовый аргумент (обозначен как num). Предполагается, что это текстовое представление целого числа. Поэтому командой

  intm = Int32.Parse(num);
в теле метода выполняется попытка преобразовать аргумент в целое число. Эта команда размещена внутри try-блока, однако ошибка класса FormatException, которая может возникнуть при попытке преобразования текста в число, в catch-блоке не обрабатывается, поскольку этот блок предназначен для обработки ошибок класса IndexOutOfRangeException. Поэтому если ошибка на этом этапе возникла, то метод завершает работу и ошибка передается во внешнюю try-catch конструкцию (если такая есть) для обработки. Но перед этим выполняется блок finally, в котором отображается значение текстовой переменной msg. Начальное значение переменной - текст "Массив не создан!". Но если преобразование текстового аргумента в число проходит успешно, а также без ошибок выполняется команда
  int[] array = new int[m];            , 
которой создается целочисленный массив, то командой
  msg = "Создан массив: ";
переменной msg присваивается новое значение, а затем в циклек командой
  msg += " " + array[k];
к текущему текстовому значению переменной дописывается значение элемента массива array с индексом k. Предварительно значение элементу присваивается командой
  array[k] = 12 / (4 - k);    . 
Важно и то, что в теле цикла индексная переменная k принимает значения от 0 до 4 включительно. Поэтому здесь возможны две неприятности. Во-первых, если размер массива меньше 4, то возникает ошибка, связанная с выходом за пределы массива. Во-вторых, если дело доходит до вычисления элемента с индексом 4 , то получаем ошибку деления на ноль. Как отмечалось выше, ошибка выхода за пределы массива обрабатывается в catch-блоке, в котором отображается сообщение соответствующего содержания. После обработки ошибки выполняется блок finally и команда, которая выводит сообщение о том, что метод завершил работу. Если же возникает ошибка деления на ноль, то она в методе не обрабатывается, а выбрасывается из метода для дальнейшей обработки внешними блоками обработки (если они есть), но перед этим выполняется блок finally.


Возможны следующие сценарии.

    При выполнении внутреннего try-блока в любом случае возникает ошибка. Это или ошибки класса FormatException, OverflowException или DivideByZeroException, выбрасываемые при вызове метода generator(), или ошибка класса IndexOutOfRangeException, возникающая при выполнении команды

  symbs[1] = 'A';             .

    В главном методе программы командой

  string[] args = { "один", "-2", "5", "2" };
создается текстовый массив. Элементы массива используются для передачи аргументами методу generator(). Также командой
  char[] symbs = new char[1];
создается вспомогательный символьный массив из одного элемента. После этого запускается конструкция цикла, в которой перебираются индексы элементов массива args.

    Тело цикла состоит из внешней try-catch конструкции и команды, отображающей сообщение о завершении выполнения соответствующей итерации цикла. Во внешнем catch-блоке обрабатываются исключения класса Exception (то есть фактически все ошибки). При обработке исключения отображается название класса ошибки.

    Внешний try-блок состоит из команды, которой отображается сообщение о начале выполнения цикла, а также внутренней конструкции try-catch-finally. Внутренний try-блок состоит из команд

  generator(args[k]);
и
  symbs[1] = 'A';     . 
Вторая из команд содержит ошибку: поскольку массив symbs состоит всего из одного элемента, то элемента с индексом 1 у массива symbs нет. Выполнение данной команды приводит к ошибке класса IndexOutOfRangeException. Но команда
  symbs[1] = 'A';     . 
выполняется, только если при выполнении команды
  generator(args[k]);
не было выброшено исключение.


При выполнении внутреннего try-блока в любом случае возникает ошибка. Это или ошибки класса FormatException, OverflowException или DivideByZeroException, выбрасываемые при вызове метода generator(), или ошибка класса IndexOutOfRangeException, возникающая при выполнении команды
  symbs[1] = 'A';             .

    Для внутреннего try-блока предусмотрены catch-блоки, обрабатывающие ошибки классов DivideByZeroException и IndexOutOfRangeException. Есть также блок finally, при выполнении которого командой

  symbs[0] = (char)('A' + k);
присваивается значение единственному элементу массива symbs, а затем значение этого элемента отображается в консольном окне.

    Чтобы понять результат выполнения программы, необходимо учесть следующие обстоятельства.

    На следующем шаге мы рассмотрим генерацию исключений.




Предыдущий шаг Содержание Следующий шаг