Обработка исключительных ситуаций важна еще и по тому, что в С# определены стандартные исключения для типичных программных ошибок, например деление на нуль или выход индекса за границы массива. Для реаги рования на подобные ошибки в программе должно быть организовано отслеживание и обработка соответствующих исключительных ситуаций. Ведь в конечном счете для успешного программирования на C# необходимо научиться умело пользоваться подсистемой обработки исключи тельных ситуаций. Класс System.Exception
В C# исключения представлены в виде классов. Все классы исключений должны быть производными от встроенного в C# класса Exception, являющегося частью про странства имен System. Следовательно, все исключения являются подклассами класса Exception.
К числу самых важных подклассов Exception относится класс SystemException. Именно от этого класса являются производными все исключения, генерируемые испол няющей системой C# (т.е. системой CLR). Класс SystemException ничего не добавляет к классу Exception, а просто определяет вершину иерархии стандартных исключений.
В среде .NET Framework определено несколько встроенных исключений, являю щихся производными от класса SystemException. Например, при попытке выпол нить деление на нуль генерируется исключение DivideByZeroException. Как будет показано далее в этой главе, в C# можно создавать собственные классы исключений, производные от класса Exception. Основы обработки исключительных ситуаций
Обработка исключительных ситуаций в C# организуется с помощью четырех клю чевых слов: try, catch, throw и finally. Они образуют взаимосвязанную подсистему, в которой применение одного из ключевых слов подразумевает применение другого. На протяжении всей этой главы назначение и применение каждого из упомянутых выше ключевых слов будет рассмотрено во всех подробностях. Но прежде необходимо дать общее представление о роли каждого из них в обработке исключительных ситуа ций. Поэтому ниже кратко описан принцип их действия.
Операторы программы, которые требуется контролировать на появление исключе ний, заключаются в блок try. Если внутри блока try возникает исключительная ситуа ция, генерируется исключение. Это исключение может быть перехвачено и обработано каким-нибудь рациональным способом в коде программы с помощью оператора, обо значаемого ключевым словом catch. Исключения, возникающие на уровне системы, генерируются исполняющей системой автоматически. А для генерирования исключе ний вручную служит ключевое слово throw. Любой код, который должен быть непре менно выполнен после выхода из блока try, помещается в блок finally. Применение пары ключевых слов try и catch
Основу обработки исключительных ситуаций в C# составляет пара ключевых слов try и catch. Эти ключевые слова действуют совместно и не могут быть использованы порознь. Ниже приведена общая форма определения блоков try/catch для обработ ки исключительных ситуаций: try { // Блок кода, проверяемый на наличие ошибок. } catch (ExcepType1 exOb) { // Обработчик исключения типа ExcepTypel. } catch (ExcepType2 exOb) { // Обработчик исключения типа ExcepType2. }
где ЕхсерТуре — это тип возникающей исключительной ситуации. Когда исключение генерируется оператором try, оно перехватывается составляющим ему пару опера тором catch, который затем обрабатывает это исключение. В зависимости от типа исключения выполняется и соответствующий оператор catch. Так, если типы гене рируемого исключения и того, что указывается в операторе catch, совпадают, то вы полняется именно этот оператор, а все остальные пропускаются. Когда исключение перехватывается, переменная исключения exOb получает свое значение.
На самом деле указывать переменную ехОb необязательно. Так, ее необязательно указывать, если обработчику исключений не требуется доступ к объекту исключения, что бывает довольно часто. Для обработки исключения достаточно и его типа. Именно поэтому во многих примерах программ, приведенных в этой главе, переменная ехОb опускается.
Следует, однако, иметь в виду, что если исключение не генерируется, то блок опера тора try завершается как обычно, и все его операторы catch пропускаются. Выполне ние программы возобновляется с первого оператора, следующего после завершающе го оператора catch. Таким образом, оператор catch выполняется лишь в том случае, если генерируется исключение. Простой пример обработки исключительной ситуации
Рассмотрим простой пример, демонстрирующий отслеживание и перехватывание исключения. Как вам должно быть уже известно, попытка индексировать массив за его границами приводит к ошибке. Когда возникает подобная ошибка, система CLR гене рирует исключение IndexOutOfRangeException, которое определено как стандарт ное для среды .NET Framework. В приведенной ниже программе такое исключение генерируется намеренно и затем перехватывается. // Продемонстрировать обработку исключительной ситуации. using System; class ExcDemol { static void Main() { int[] nums = new int[4]; try { Console.WriteLine("До генерирования исключения."); // Сгенерировать исключение в связи с выходом индекса за границы массива. for(int i=0; i < 10; i++) { nums[i] = i; Console.WriteLine("nums[(0)]: {1}", i, nums[i]); } Console.WriteLine("He подлежит выводу"); } catch (IndexOutOfRangeException) { // Перехватить исключение. Console.WriteLine("Индекс вышел за границы массива!"); } Console.WriteLine("После блока перехвата исключения."); } }
При выполнении этой программы получается следующий результат. До генерирования исключения. nums[0]: 0 nums[1]: 1 nums[2]: 2 nums[3]: 3 Индекс вышел за границы массива! После блока перехвата исключения.
В данном примере массив nums типа int состоит из четырех элементов. Но в цикле for предпринимается попытка проиндексировать этот массив от 0 до 9, что и приво дит к появлению исключения IndexOutOfRangeException, когда происходит обра щение к элементу массива по индексу 4.
Несмотря на всю свою краткость, приведенный выше пример наглядно демон стрирует ряд основных моментов процесса обработки исключительных ситуаций. Во-первых, код, который требуется контролировать на наличие ошибок, содержится в блоке try. Во-вторых, когда возникает исключительная ситуация (в данном случае — при попытке проиндексировать массив nums за его границами в цикле for), в блоке try генерируется исключение, которое затем перехватывается в блоке catch. В этот момент выполнение кода в блоке try завершается и управление передается блоку catch. Это означает, что оператор catch не вызывается специально, а выполнение кода переходит к нему автоматически. Следовательно, оператор, содержащий метод WriteLine() и следующий непосредственно за циклом for, где происходит выход индекса за границы массива, вообще не выполняется. А в задачу обработчика исклю чений входит исправление ошибки, приведшей к исключительной ситуации, чтобы продолжить выполнение программы в нормальном режиме.
Обратите внимание на то, что в операторе catch указан только тип исключения (в данном случае — IndexOutOfRangeException), а переменная исключения отсут ствует. Как упоминалось ранее, переменную исключения требуется указывать лишь в том случае, если требуется доступ к объекту исключения. В ряде случаев значение объекта исключения может быть использовано обработчиком исключений для по лучения дополнительной информации о самой ошибке, но зачастую для обработки исключительной ситуации достаточно просто знать, что она произошла. Поэтому переменная исключения нередко отсутствует в обработчиках исключений, как в рас сматриваемом здесь примере.
Как пояснялось ранее, если исключение не генерируется в блоке try, то блок catch не выполняется, а управление программой передается оператору, следующему после блока catch. Для того чтобы убедиться в этом, замените в предыдущем примере про граммы строку кода for(int i=0; i < 10; i++) {
на строку for(int i=0; i < nums.Length; i++) {
Теперь индексирование массива не выходит за его границы в цикле for. Следова тельно, никакого исключения не генерируется и блок catch не выполняется. Второй пример обработки исключительной ситуации
Следует особо подчеркнуть, что весь код, выполняемый в блоке try, контролирует ся на предмет исключительных ситуаций, в том числе и тех, которые могут возникнуть в результате вызова метода из самого блока try. Исключение, генерируемое методом в блоке try, может быть перехвачено в том же блоке, если, конечно, этого не будет сделано в самом методе.
В качестве еще одного примера рассмотрим следующую программу, где блок try помещается в методе Main(). Из этого блока вызывается метод GenException(), в ко тором и генерируется исключение IndexOutOfRangeException. Это исключение не перехватывается методом GenException(). Но поскольку метод GenException() вы зывается из блока try в методе Main(), то исключение перехватывается в блоке catch, связанном непосредственно с этим блоком try. /* Исключение может быть сгенерировано одним методом и перехвачено другим. */ using System; class ExcTest { // Сгенерировать исключение. public static void GenException() { int[] nums = new int[4]; Console.WriteLine("До генерирования исключения."); // Сгенерировать исключение в связи с выходом индекса за границы массива. for(int i=0; i < 10; i++) { nums[i] = i; Console.WriteLine("nums [{0}] : {1}", i, nums[i]); } Console.WriteLine("He подлежит выводу"); } } class ExcDemo2 { static void Main() { try { ExcTest.GenException(); } catch (IndexOutOfRangeException) { // Перехватить исключение. Console.WriteLine("Индекс вышел за границы массива!"); } Console.WriteLine("После блока перехвата исключения."); } }