Урок 32. Обработка исключений C#


На 32-м уроке по C# мы начнем обзор обработки исключений. При возникновении непредвиденного события необработанные исключения приводят к аварийному завершению работы программы. Правильная обработка исключений позволяет выйти из ошибки без поломки программы или даже сеанса системы.

Что такое исключение?

Исключение - это ошибка, возникающая вемя выполнения программы. Как правило, исключение описывает непредвиденное событие. Например, исключение возникает, если программа запрашивает больше памяти, чем может предоставить операционная система. Это исключение называется исключением нехватки памяти.

Иерархия классов исключений

При возникновении исключения нормальная обработка кода прекращается. Создается объект, содержащий информацию, относящуюся к ошибке. Этот объект является экземпляром класса исключений. Самым основным из классов исключений является Exception, определенный в пространстве имен System.

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

Примечание: подкласс и базовый класс-это термины объектно-ориентированного программирования.

Системные исключения

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

  • ArithmeticException. Выбрасывается при возникновении ошибки во время арифметической операции, приведения или преобразования.
  • DivideByZeroException. Возникает при попытке разделить значение на ноль. Является более специализированной версией исключения выше.
  • OverflowException. Выбрасывается, когда ошибка возникает во время арифметической операции или во время литья или преобразования, потому что результирующее значение слишком велико или мало. Исключение OverflowException является производным от исключения ArithmeticException.
  • OutOfMemoryException. Выбрасывается, когда доступной памяти недостаточно для продолжения выполнения.
Приведенный выше список дает небольшую выборку классов исключений в иерархии. Гораздо более полный список можно найти на странице иерархии системных исключений веб-сайта Microsoft MSDN.

Исключения приложений

Исключения приложений - это те, которые вы определяете. Они могут быть универсальными, например WordProcessorException, или специализированными, например LeftMarginTooSmallException. Конечно, ни одно из этих исключений не существует в .NET framework; они должны быть созданы перед использованием. Исключения приложений не будут обсуждаться до следующей статьи, в которой исследуются программные исключения.

Обработка исключений

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

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

Основной блок Try / Catch

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

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

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

static void Main(string[] args)
{
    int value = 50;
    int divisor = 0;
    int calculated;
 
    try
    {
        calculated = value / divisor;
    }
    catch
    {
        Console.WriteLine("Произошла ошибка во время деления.");
        calculated = int.MaxValue;
    }
 
    Console.WriteLine("Result = {0}", calculated);
}
 
/* OUTPUT
 
Произошла ошибка во время деления 
Result = 2147483647
 
*/
Извлечение информации об исключении

Как написано выше, создание исключения включает в себя создание объекта исключения. Объект имеет свойства, содержащие информацию, описывающую ошибку. Это может быть прочитано кодом в блоке catch путем добавления объявления исключения к оператору catch. Следующий пример расширяет предыдущий код, объявляя объект наиболее обобщенного класса исключений. Все исключения будут пойманы, но теперь информация об исключении может быть использована при создании сообщения об ошибке.

static void Main(string[] args)
{
    int value = 50;
    int divisor = 0;
    int calculated;
 
    try
    {
        calculated = value / divisor;
    }
    catch (Exception ex)                        // Ловим
    {
        Console.WriteLine("Произошла ошибка во время деления.");
        Console.WriteLine(ex.Message);          // Вывод
        calculated = int.MaxValue;
    }
 
    Console.WriteLine("Result = {0}", calculated);
}
 
/* OUTPUT
 
Произошла ошибка во время деления 
Attempted to divide by zero.
Result = 2147483647
 
*/
Приведенный выше пример улавливает любое исключение и заполняет объект класса Exception . Свойство объекта Message используется для вывода описания ошибки. Это одно из нескольких свойств, предоставляемых всеми классами исключений. Некоторые из самых полезных свойств являются:

  • Message. Строка, содержащая описание исключения.
  • Source. Строка, содержащая имя программы или объекта, вызвавшего исключение.
  • TargetSite. Объект, содержащий сведения о методе, вызвавшем исключение.
  • StackTrace. Строка, содержащая полный стек вызовов, которые привели к исключению. Эта строка позволяет программисту просматривать каждый вызов метода, выполненный до возникновения исключения. Это особенно полезно во время тестирования и отладки.
  • InnerException. Когда одно исключение возникает как прямой результат другого, начальное исключение может содержаться в этом свойстве. Внутреннее исключение содержит все стандартные свойства, включая, возможно,еще одно внутреннее исключение. Если нет внутреннего исключения, это свойство имеет значение null.


Примечание: более специализированные типы исключений включают дополнительную релевантную информацию. Например, исключение ArgumentException включает свойство ParamName, детализирующее рассматриваемый параметр.

static void Main(string[] args)
{
    int value = 50;
    int divisor = 0;
    int calculated;
 
    try
    {
        calculated = value / divisor;
    }
    catch (Exception ex)
    {
        Console.WriteLine("Сообщение:    {0}\n", ex.Message);
        Console.WriteLine("Источник:     {0}\n", ex.Source);
        Console.WriteLine("Метод: {0}\n", ex.TargetSite.Name);
        Console.WriteLine("StackTrace: {0}\n", ex.StackTrace);
 
        calculated = int.MaxValue;
    }
 
    Console.WriteLine("Result = {0}", calculated);
}
 
/* Вывод
 
Сообщение:    Attempted to divide by zero.
 
Источник:     ConsoleApplication1
 
Метод: Void Main(System.String[])
 
StackTrace: at ConsoleApplication1.Program.Main(String[] args) in
            C:\...\Program.cs:line 17
 
Result = 2147483647
 
*/
Как ловить конкретные исключения

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

static void Main(string[] args)
{
    int value = 50;
    int divisor = 0;
    int calculated;
 
    try
    {
        calculated = value / divisor;
    }
    catch (DivideByZeroException ex)     // поймать только конкретное исключение
    {
        Console.WriteLine("произошло деление на ноль.");
        Console.WriteLine(ex.Message);          // сообщить об ошибке
        calculated = int.MaxValue;
    }
 
    Console.WriteLine("Result = {0}", calculated);
}
 
/* OUTPUT
 
произошло деление на ноль.
Attempted to divide by zero.
Result = 2147483647
 
*/
Перехват определенных типов исключений обеспечивает два преимущества. Во-первых, неожиданные исключения, такие как нехватка памяти, не улавливаются и неправильно интерпретируются или маскируются, вызывая неожиданные побочные эффекты. Во-вторых, все дополнительные свойства, связанные со специализированным классом исключений, становятся доступными в блоке catch.

Примечание: если достаточно поймать тип исключения и нет необходимости опрашивать свойства исключения, то нет необходимости включать имя переменной для объекта исключения. Catch в приведенном выше примере может быть сокращен до catch (DivideByZeroException) в такой ситуации.

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

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

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

В следующем примере представлены три блока catch. Первый обрабатывает деление на ноль ошибок. Второй отвечает на общее арифметическое исключение, но не используется, если происходит деление на ноль. Окончательный catch не указывает тип исключения для перехвата, поэтому выполняется для всех других исключений. после блока try / catch, даже если возникает исключение или блок try содержит оператор return.

static void Main(string[] args)
{
    int value = 50;
    int divisor = 0;
    int calculated;
 
    try
    {
        calculated = value / divisor;
    }
    catch (DivideByZeroException)            
    {
        Console.WriteLine("Division by zero occurred.");
        calculated = int.MaxValue;
    }
    catch (ArithmeticException)                 
    {
        Console.WriteLine("An arithmetic exception occurred.");
        calculated = int.MaxValue;
    }
    catch
    {
        Console.WriteLine("An unexpected exception occurred.");
        calculated = int.MaxValue;
    }
 
    Console.WriteLine("Result = {0}", calculated);
}
Блок Try / Catch / Finally

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

C# определяет блок добавления, который может быть добавлен в конец структуры try / catch. Это последний блок. Код в этом разделе гарантированно выполняется после блока try / catch, даже если возникает исключение или блок try содержит оператор return.

static void Main(string[] args)
{
    int value = 50;
    int divisor = 0;
    int calculated;
 
    try
    {
        calculated = value / divisor;
    }
    catch
    {
        Console.WriteLine("An error occurred during division.");
        calculated = int.MaxValue;
    }
    finally
    {
        Console.WriteLine("Clearing up any resources.");
    }
 
    Console.WriteLine("Result = {0}", calculated);
}
Можно попробовать использовать Finally вместе с блоками catch. Если при использовании такой структуры возникает исключение, оно остается необработанным и передается вызывающей подпрограмме или системе времени выполнения C#. Однако код в блоке finally выполняется независимо от того, вызвано исключение или нет.
Автор этого материала - я - Пахолков Юрий. Я оказываю услуги по написанию программ на языках Java, C++, C# (а также консультирую по ним) и созданию сайтов. Работаю с сайтами на CMS OpenCart, WordPress, ModX и самописными. Кроме этого, работаю напрямую с JavaScript, PHP, CSS, HTML - то есть могу доработать ваш сайт или помочь с веб-программированием. Пишите сюда.

тегистатьи IT, уроки по си шарп, си шарп, исключения, ошибки




Отправляя сообщение я подтверждаю, что ознакомлен и согласен с политикой конфиденциальности данного сайта.




Урок 11. Перечисления Java
Программист и системный администратор: два в одном
Что такое Java или особенности языка