Шаг 222.
Язык программирования C#. Начала.
Обработка исключений. Основные классы исключений

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

    Как отмечалось на предыдущих шагах, существует целая иерархия классов, описывающих различные исключительные ситуации. Большинство практически важных классов исключений содержатся в пространстве имен System. На вершине иерархии наследования классов находится класс Exception. Производными от класса Exception являются классы SystemException и ApplicationException. Класс SystemException является базовым для классов исключений, соответствующих системным ошибкам. Класс ApplicationException обычно используют для создания пользовательских исключений (этот вопрос мы рассмотрим немного позже). В таблице 1 приведены некоторые классы исключений, которые нередко используются на практике.

Таблица 1. Некоторые классы исключений
Класс Описание
ArgumentException Исключение, связанное с некорректным аргументом
ArithmeticException Исключение, связанное с выполнением арифметических операций. У этого класса имеется три производных класса:
  • DivideByZeroException - ошибка деления на ноль,
  • NotFiniteNumberException - получено бесконечное значение и
  • OverflowException - ошибка, связанная с переполнением
ArrayTypeMismatchException Исключение, связанное с попыткой записать в массив значение недопустимого типа
FormatException Исключение, обусловленное недопустимым форматом
IndexOutOfRangeException Исключение, связанное с выходом индекса за допустимые пределы
InvalidCastException Исключение, обусловленное недопустимым преобразованием типов
InvalidProgramException Исключение, связанное с неправильной компиляцией программы
MemberAccessException Исключение, обусловленное неудачной попыткой доступа к члену класса. У класса этого исключения есть три производных класса:
  • FieldAccessException - ошибка доступа к полю,
  • MethodAccessException - ошибка доступа к методу и
  • MissingMemberException - отсутствует член класса
NotImplementedException Исключение, связанное с попыткой выполнить нереализованный метод или операцию
NotSupportedException Исключение, связанное с попыткой выполнения неподдерживаемого метода или операции
NullReferenceException Исключение, связанное с использованием пустой ссылки
OutOfMemoryException Исключение, связанное с недостаточным объемом памяти
RankException Исключение, связанное с передачей массива неправильной размерности
StackOverflowException Исключение, связанное с переполнением стека
TimeoutException Исключение, связанное с окончанием времени, выделенного на выполнение процесса или операции

    Конечно, это лишь небольшая часть классов исключений. В действительности иерархия классов наследования более чем обширна, она содержит самые разнообразные классы, так сказать, "на все случаи жизни". Конкретные классы мы будем использовать и обсуждать по мере необходимости. А далее рассмотрим пример, в котором при выполнении программы генерируются (и обрабатываются) исключения разных типов. Обратимся к программному коду:

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

namespace pr222_1
{
    // Класс с главным методом:
    class Program
    {
        // Главный метод:
        static void Main()
        {
            // Объект для генерации случайных чисел:
            Random rnd = new Random();
            // Переменная массива: 
            int[] nums;
            // Целочисленные переменные: 
            int x, n;
            // Конструкция цикла: 
            for (int k = 1; k < 10; k++)
            {
                // Отображение номера итерации цикла:
                Console.Write("[{0}] ", k);
                // Начальное значение переменной (номер команды): 
                n = 1;
                // Контролируемый блок кода: 
                try
                {
                    // Команда №1. Попытка создать массив: 
                    nums = new int[2 * rnd.Next(3) - 1];
                    // Увеличение значения счетчика команд: 
                    n++;
                    // Команда №2. Попытка вычислить частное: 
                    x = 1 / rnd.Next(3);
                    // Увеличение значения счетчика команд: 
                    n++;
                    // Команда №3. Попытка присвоить значение 
                    // элементу массива: 
                    nums[rnd.Next(2) - 1] = x;
                    // Увеличение значения счетчика команд: 
                    n++;
                    // Команда №4. Ошибка преобразования: 
                    nums[0] = Int32.Parse("ноль");
                }
                // Блок обработки исключений: 
                catch (Exception e)
                {
                    // Отображение номера команды с ошибкой:
                    Console.Write("Команда №{0}: ", n);
                    // Отображение типа (класса) исключения: 
                    Console.Write(e.GetType().Name);
                    // Отображение описания для исключения:
                    Console.WriteLine(" - " + e.Message);
                }
            }
            // Задержка:
            Console.ReadLine();
        }
    }
}
Архив проекта можно взять здесь.

    Возможный результат выполнения программы (с поправкой на то, что используется генератор случайных чисел) представлен ниже:


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

    Программа, в общем-то, простая. В главном методе командой

  Random rnd = new Random();
создается объект rnd класса Random для генерации случайных чисел. Также мы объявляем переменную nums, которая является переменной целочисленного одномерного массива (может ссылаться на одномерный целочисленный массив). Также мы объявляем целочисленные переменные x и n, которые нам нужны для проведения вычислений.

    Затем запускается цикл, в котором индексная переменная k последовательно принимает значения от 1 до 9 включительно. В начале цикла командой

  Console.Write("[{0}] ", k);
отображается номер итерации цикла, а переменная n получает начальное (для данной итерации) единичное значение (команда
  n = 1;
). После этого следует try-блок с контролируемым кодом. Там есть несколько команд, которые потенциально могут привести к возникновению ошибки (а одна команда просто вызывает ошибку). Перед каждой такой командой (за исключением первой) выполняется команда
  n++      . 
Это позволяет нам вести учет потенциально опасных команд, а в случае возникновения ошибки мы будем знать, какая из четырех "опасных" команд стала фатальной.

    Итак, сначала командой

  nums = new int[2 * rnd.Next(3) - 1];
выполняется попытка создать массив. Она не всегда успешная. Размер массива определяется выражением 2*rnd.Next(3)-1. Значение выражения rnd.Next(3) - это случайное целое число в диапазоне от 0 до 2 включительно (то есть 0, 1 или 2). Поэтому значением выражения 2*rnd.Next(3)-1 с одинаковой вероятностью является число -1, 1 или 3. Если это число -1, то мы пытаемся создать массив отрицательного размера. В этом случае генерируется исключение класса OverflowException. Но если на этом этапе все прошло нормально, то затем выполняется команда
  x = 1 / rnd.Next(3);    . 
Здесь мы пытаемся присвоить значение целочисленной переменной x. Мы уже знаем, что значение выражения rnd.Next(3) есть число 0, 1 или 2. Если это число 1 или 2, то выполняется команда деления нацело и переменная x получает значение (1 или 0, но это не принципиально). Но если значение выражения окажется равным 0, то при вычислении значения переменной x возникает исключение класса DivideByZeroException, связанное с попыткой деления на ноль. Но если и тут повезло и ошибки не было, то она может возникнуть при выполнении команды
  nums[rnd.Next(2) - 1] = x;     . 
Дело в том, что выражение rnd.Next(2)-1 принимает с равной вероятностью значения -1 и 0. Индекс, выходящий за пределы допустимого диапазона (в том числе и отрицательный индекс), - это исключение класса IndexOutOfRangeException. Если же на этом этапе тоже все в порядке, то на команде
  nums[0] = Int32.Parse("ноль");
везение точно заканчивается. Здесь предпринята попытка преобразовать в целое число текст "ноль", что приводит к исключению класса FormatException.

    Таким образом, при выполнении команд тела цикла точно генерируется исключение одного из четырех упомянутых выше типов. Такое исключение перехватывается и обрабатывается в catch-блоке. В нем отображается номер команды, при выполнении которой возникла ошибка (значение переменной n), класс исключения (выражение e.GetType().Name), а также описание возникшей ошибки (выражение e.Message).

    На следующем шаге мы рассмотрим спользование нескольких catch-блоков.




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