Вызвать исключение c#. Основы обработки исключений. Выбрасывание исключений. Создание объектов Exception

Описывать е как «константу, приблизительно равную 2,71828…» - это все равно, что называть число пи «иррациональным числом, приблизительно равным 3,1415…». Несомненно, так и есть, но суть по-прежнему ускользает от нас.

Число пи - это соотношение длины окружности к диаметру, одинаковое для всех окружностей . Это фундаментальная пропорция, свойственная всем окружностям, а следовательно, она участвует в вычислении длины окружности, площади, объема и площади поверхности для кругов, сфер, цилиндров и т.д. Пи показывает, что все окружности связаны, не говоря уже о тригонометрических функциях, выводимых из окружностей (синус, косинус, тангенс).

Число е является базовым соотношением роста для всех непрерывно растущих процессов. Число е позволяет взять простой темп прироста (где разница видна только в конце года) и вычислить составляющие этого показателя, нормальный рост, при котором с каждой наносекундой (или даже быстрее) всё вырастает еще на немного.

Число е участвует как в системах с экспоненциальным, так и постоянным ростом: население, радиоактивный распад, подсчет процентов, и много-много других. Даже ступенчатые системы, которые не растут равномерно, можно аппроксимировать с помощью числа е.

Также, как любое число можно рассматривать в виде «масштабированной» версии 1 (базовой единицы), любую окружность можно рассматривать в виде «масштабированной» версии единичной окружности (с радиусом 1). И любой коэффициент роста может быть рассмотрен в виде «масштабированной» версии е («единичного» коэффициента роста).

Так что число е – это не случайное, взятое наугад число. Число е воплощает в себе идею, что все непрерывно растущие системы являются масштабированными версиями одного и того же показателя.

Понятие экспоненциального роста

Давайте начнем с рассмотрения базовой системы, которая удваивается за определенный период времени. Например:

  • Бактерии делятся и «удваиваются» в количестве каждые 24 часа
  • Мы получаем вдвое больше лапшинок, если разламываем их пополам
  • Ваши деньги каждый год увеличиваются вдвое, если вы получаете 100% прибыли (везунчик!)

И выглядит это примерно так:

Деление на два или удваивание – это очень простая прогрессия. Конечно, мы можем утроить или учетверить, но удваивание более удобно для пояснения.

Математически, если у нас есть х разделений, мы получаем в 2^x раз больше добра, чем было вначале. Если сделано только 1 разбиение, получаем в 2^1 раза больше. Если разбиений 4, у нас получится 2^4=16 частей. Общая формула выглядит так:

рост = 2 x

Другими словами, удвоение – это 100% рост. Мы можем переписать эту формулу так:

рост = (1+100%) x

Это то же равенство, мы только разделили «2» на составные части, которыми в сущности и является это число: начальное значение (1) плюс 100%. Умно, да?

Конечно, мы можем подставить и любое другое число (50%, 25%, 200%) вместо 100% и получить формулу роста для этого нового коэффициента. Общая формула для х периодов временного ряда будет иметь вид:

рост = (1+прирост ) x

Это просто означает, что мы используем норму возврата, (1 + прирост), «х» раз подряд.

Приглядимся поближе

Наша формула предполагает, что прирост происходит дискретными шагами. Наши бактерии ждут, ждут, а потом бац!, и в последнюю минуту они удваиваются в количестве. Наша прибыль по процентам от депозита магическим образом появляется ровно через 1 год. На основе формулы, написанной выше, прибыль растет ступенчато. Зеленые точки появляются внезапно.

Но мир не всегда таков. Если мы увеличим картинку, мы увидим, что наши друзья-бактерии делятся постоянно:

Зеленый малый не возникает из ничего: он медленно вырастает из синего родителя. После 1 периода времени (24 часа в нашем случае), зеленый друг уже полностью созрел. Повзрослев, он стает полноценным синим членом стада и может создавать новые зеленые клеточки сам.

Эта информация как-то изменит наше уравнение?

Не-а. В случае с бактериями, полусформированные зеленые клетки все же не могут ничего делать, пока не вырастут и совсем не отделятся от своих синих родителей. Так что уравнение справедливо.

Перехват исключений

Принимая во внимание, что.NET Framework включает большое количество предопределенных классов исключений, возникает вопрос: как их использовать в коде для перехвата ошибочных условий? Для того чтобы справиться с возможными ошибочными ситуациями в коде C#, программа обычно делится на блоки трех разных типов:

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

    Блоки catch инкапсулируют код, который обрабатывает ошибочные ситуации, происходящие в коде блока try. Это также удобное место для протоколирования ошибок.

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

Try и catch

Основу обработки исключительных ситуаций в C# составляет пара ключевых слов try и catch. Эти ключевые слова действуют совместно и не могут быть использованы порознь. Ниже приведена общая форма определения блоков try/catch для обработки исключительных ситуаций:

try { // Блок кода, проверяемый на наличие ошибок. } catch (ExcepType1 exOb) { // Обработчик исключения типа ExcepType1. } catch (ExcepType2 exOb) { // Обработчик исключения типа ExcepType2. } ...

где ExcepType - это тип возникающей исключительной ситуации. Когда исключение генерируется оператором try, оно перехватывается составляющим ему пару оператором catch, который затем обрабатывает это исключение. В зависимости от типа исключения выполняется и соответствующий оператор catch. Так, если типы генерируемого исключения и того, что указывается в операторе catch, совпадают, то выполняется именно этот оператор, а все остальные пропускаются. Когда исключение перехватывается, переменная исключения exOb получает свое значение. На самом деле указывать переменную exOb необязательно. Так, ее необязательно указывать, если обработчику исключений не требуется доступ к объекту исключения, что бывает довольно часто. Для обработки исключения достаточно и его типа.

Следует, однако, иметь в виду, что если исключение не генерируется, то блок оператора try завершается как обычно, и все его операторы catch пропускаются. Выполнение программы возобновляется с первого оператора, следующего после завершающего оператора catch. Таким образом, оператор catch выполняется лишь в том случае, если генерируется исключение.

Давайте рассмотрим пример, в котором будем обрабатывать исключение, возникающее при делении числа на 0:

Using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace ConsoleApplication1 { class Program { static int MyDel(int x, int y) { return x / y; } static void Main() { try { Console.Write("Введите x: "); int x = int.Parse(Console.ReadLine()); Console.Write("Введите y: "); int y = int.Parse(Console.ReadLine()); int result = MyDel(x, y); Console.WriteLine("Результат: " + result); } // Обрабатываем исключение возникающее при делении на ноль catch (DivideByZeroException) { Console.WriteLine("Деление на 0 detected!!!\n"); Main(); } // Обрабатываем исключение при неккоректном вводе числа в консоль catch (FormatException) { Console.WriteLine("Это НЕ число!!!\n"); Main(); } Console.ReadLine(); } } }

Данный простой пример наглядно иллюстрирует обработку исключительной ситуации при делении на 0 (DivideByZeroException), а так же пользовательскую ошибку при вводе не числа (FormatException).

Последствия неперехвата исключений

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

константам условной компиляции . Он разрешает определять константы, используемые позже в методе условной печати WriteIf классов Debug и Trace . Мощь этого механизма в том, что константы можно менять в файле конфигурации проекта, не изменяя код проекта и не требуя его перекомпиляции.

Отладка и инструментальная среда Visual Studio .Net

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

Обработка исключительных ситуаций

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

Что делать при возникновении исключительной ситуации ? Конечно, всегда есть стандартный способ - сообщить о возникшей ошибке и прервать выполнение программы. Понятно, что это приемлемо лишь для безобидных приложений; даже для компьютерных игр этот способ не годится, что уж говорить о критически важных приложениях!

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

Обработка исключений в языках C/C++

Для стиля программирования на языке C характерно описание методов класса как булевых функций, возвращающих true в случае нормального завершения метода и false - при возникновении исключительной ситуации . Вызов метода встраивался в If -оператор, обрабатывающий ошибку в случае неуспеха завершения метода:

bool MyMethod(...){...} if !MyMethod(){// обработка ошибки} {//нормальное выполнение}

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

Поэтому в C/C++ применяется схема try/catch блоков, суть которой в следующем. Участок программы, в котором может возникнуть исключительная ситуация , оформляется в виде охраняемого try-блока. Если при его выполнении возникает исключительная ситуация , то происходит прерывание выполнения try-блока c классификацией исключения. Это исключение начинает обрабатывать один из catch-блоков, соответствующий типу исключения . В C/C++ применяются две такие схемы. Одна из них - схема с возобновлением - соответствует так называемым структурным, или С-исключениям. Вторая схема - без возобновления - соответствует С++ исключениям. В первой схеме обработчик исключения - catch-блок - возвращает управление в некоторую точку try-блока. Во второй схеме управление не возвращается в try-блок.

С некоторыми синтаксическими отличиями схема с возобновлением применяется в языках VB/VBA.

Схема обработки исключений в C#

Язык C# наследовал схему исключений языка С++, внеся в нее свои коррективы. Рассмотрим схему подробнее и начнем с синтаксиса конструкции try-catch-finally :

try {...} catch (T1 e1) {...} ... catch(Tk ek) {...} finally {...}

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

Выбрасывание исключений. Создание объектов Exception

В теле try-блока может возникнуть исключительная ситуация , приводящая к выбрасыванию исключений . Формально выбрасывание исключения происходит при выполнении оператора throw . Этот оператор, чаще всего, выполняется в недрах операционной системы, когда система команд или функция API не может сделать свою работу. Но этот оператор может быть частью программного текста try-блока и выполняться, когда в результате проведенного анализа становится понятным, что дальнейшая нормальная работа невозможна.

Синтаксически оператор throw имеет вид:

throw[выражение]

Выражение throw задает объект класса, являющегося наследником класса Exception . Обычно это выражение new , создающее новый объект. Если оно отсутствует, то повторно выбрасывается текущее исключение. Если исключение выбрасывается операционной системой, то она сама классифицирует исключение, создает объект соответствующего класса и автоматически заполняет его поля.

В рассматриваемой нами модели исключения являются объектами, класс которых представляет собой наследника класса Exception . Этот класс и многочисленные его наследники является частью библиотеки FCL, хотя и разбросаны по разным пространствам имен. Каждый класс задает определенный тип исключения в соответствии с классификацией, принятой в Framework .Net. Вот лишь некоторые классы исключений из пространства имен System : ArgumentException , ArgumentOutOfRangeException , ArithmeticException , BadImageFormatException , DivideByZeroException , OverflowException . В пространстве имен System.IO собраны классы исключений , связанных с проблемами ввода-вывода: DirectoryNotFoundException , FileNotFoundException и многие другие. Имена всех классов исключений заканчиваются словом Exception . Разрешается создавать собственные классы исключений , наследуя их от класса Exception .

При выполнении оператора throw создается объект te , класс TE которого характеризует текущее исключение, а поля содержат информацию о возникшей исключительной ситуации . Выполнение оператора throw приводит к тому, что нормальный процесс вычислений на этом прекращается. Если это происходит в охраняемом try-блоке, то начинается этап "захвата" исключения одним из обработчиков исключений.

Захват исключения

Блок catch - обработчик исключения имеет следующий синтаксис:

catch (T e) {...}

Класс T , указанный в заголовке catch -блока, должен принадлежать классам исключений . Блок catch с формальным аргументом e класса T потенциально способен захватить текущее исключение te класса TE , если и только если объект te совместим по присваиванию c объектом e. Другими словами, потенциальная способность захвата означает допустимость присваивания e = te , что возможно, когда класс TE является потомком класса T . Обработчик, класс T которого является классом Exception , является универсальным обработчиком , потенциально он способен захватить любое исключение, поскольку все они являются его потомками.

Потенциальных захватчиков может быть много, исключение захватывает лишь один - тот из них, кто стоит первым в списке проверки. Каков порядок проверки? Он довольно естественный. Вначале проверяются обработчики в порядке следования их за try -блоком, и первый потенциальный захватчик становится активным, захватывая исключение и выполняя его обработку. Отсюда становится ясно, что порядок следования в списке catch -блоков крайне важен. Первыми идут наиболее специализированные обработчики , далее по мере возрастания универсальности. Так, вначале должен идти обработчик исключения DivideByZeroException , а уже за ним - Main . Если и в ней нет потенциального захватчика исключения, то сработает стандартный обработчик, прерывающий выполнение программы с выдачей соответствующего сообщения.




Top