Net framework обработка ошибок

Содержание

  • Исключения (Exceptions) и инструкция try
  • Оговорка catch
  • Блок finally
  • Инструкция using
  • Выбрасывание исключений
  • Основные свойства System.Exception
  • Основные типы исключений
  • Директивы препроцессора
    • Pragma Warning
    • Атрибут Conditional
  • Классы Debug и Trace
    • TraceListener
    • Fail и Assert

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

Исключения (Exceptions) и инструкция try

Инструкция try отмечает блок кода как объект для обработки ошибок или очистки. После блока try обязательно должен идти либо блок catch, либо блок finally, либо они оба. Блок catch выполняется, когда внутри блока try возникает ошибка. Блок finally выполняется после того, как прекращает выполнять блок try (или, если присутствует, блок catch), независимо от того, выполнился ли он до конца или был прерван ошибкой, что позволяет выполнить так называемый код очистки.

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

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

В целом конструкция try выглядит следующим образом:

try

{

  ... // в пределах этого блока может быть выброшено исключение

}

catch (ExceptionA ex)

{

  ... // обработчик исключений типа ExceptionA

}

catch (ExceptionB ex)

{

  ... // обработчик исключений типа ExceptionB

}

finally

{

  ... // код очистки

}

Например, следующий код выбросит ошибку DivideByZeroException (поскольку делить на ноль нельзя) и наша программа завершить досрочно:

int x = 3, y = 0;

Console.WriteLine (x / y);

Чтобы этого избежать можно использовать конструкцию try:

try

{

  int x = 3, y = 0;

  Console.WriteLine (x / y);

}

catch (DivideByZeroException ex)

{

  Console.Write («y cannot be zero. «);

}

// выполнение программы продолжится отсюда

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

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

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

Оговорка catch

В оговорке catch указывается какой тип исключения она должна перехватывать. Это может быть либо System.Exception, либо его производный класс. Перехватывая непосредственно System.Exception, мы перехватим все возможные ошибки.  Это может быть полезно в нескольких случаях:

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

Однако обычно перехватываются исключения более специфического типа, чтобы избежать ситуации, когда обработчику ошибки придется иметь дело с исключением, для которого он не предназначен (например, OutOfMemoryException).

Можно обработать несколько типов исключений с помощью нескольких оговорок catch:

try

{

  DoSomething();

}

catch (IndexOutOfRangeException ex) { ... }

catch (FormatException ex) { ... }

catch (OverflowException ex) { ... }

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

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

catch (StackOverflowException) // без переменной

{ ... }

Более того, в оговорке catch можно опустить и переменную и тип исключения — такая оговрка будет перехватывать все исключения:

Блок finally

Блок finally выполняется всегда, независимо от того выброшено исключение или нет. Блок finally обычно содержит код очистки.

Блок finally выполняется в следующих случаях:

  • после завершения блока catch
  • если выполнение блока try прервано jump-инструкциями: return, goto и т.д.
  • после выполнения блока try полностью, если исключений так и не было выброшено

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

static void ReadFile()

{

  StreamReader reader = null;

  try

  {

      reader = File.OpenText («file.txt»);

      if (reader.EndOfStream) return;

      Console.WriteLine (reader.ReadToEnd());

  }

  finally

  {

      if (reader != null) reader.Dispose();

  }

}

В пример для закрытия файла вызывается метод Dispose. Использование этого метода внутри блока finally является стандартной практикой. C# даже позволяет заменить всю конструкцию инструкцией using.

Инструкция using

Многие классы инкапсулируют неуправляемые ресурсы, такие как дескриптор файла, соединение с базой данных и т.д. Эти классы реализуют интерфейс System.IDisposable, который содержит единственный метод без параметров Dispose, освобождающий соответствующие машинные ресурсы. Инструкция using предусматривает удобный синтаксис вызова метода Dispose для объектов реализующих IDisposable внутри блока finally:

using (StreamReader reader = File.OpenText («file.txt»))

{

  ...

}

Что эквивалентно следующей конструкции:

StreamReader reader = File.OpenText («file.txt»);

try

{

  ...

}

finally

{

  if (reader != null) ((IDisposable)reader).Dispose();

}

Выбрасывание исключений

Исключение может быть выброшено автоматически во время выполнения программы либо явно в коде программы с помощью ключевого слова throw:

static void Display (string name)

{

  if (name == null)

  throw new ArgumentNullException («name»);

  Console.WriteLine (name);

}

Также исключение может быть выброшено повторно внутри блока catch:

try { ... }

catch (Exception ex)

{

  // логирование ошибки

  ...

  throw; // повторное выбрасывание того же самого исключения

}

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

Если throw заменить на throw ex, то пример по прежнему будет работать, но свойство исключения StackTrace не будет отражать исходную ошибку.

Другой распространенный сценарий использования повторного выбрасывания исключения — повторное выбрасывание более специфического и конкретного типа исключения, чем было перехвачено ранее:

try

{

  ... // парсинг даты рождения из xml-данных

}

catch (FormatException ex)

{

  throw new XmlException («Неправильная дата рождения», ex);

}

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

Основные свойства System.Exception

К наиболее важным свойствам класса System.Exception можно отнести:

  • StackTrace — строка, представляющая все методы, которые были вызваны, начиная с того, в котором было выброшено исключение, и заканчивая тем, в котором содержится блок catch, перехвативший исключение;
  • Message — строка с описанием ошибки;
  • InnerException — содержит ссылку на объект Exeption, который вызвал текущее исключение (например, при повторном выбрасывании исключения).

Основные типы исключений

Следующие типы исключений являются наиболее распространенными в среде CLR и .NET Framework. Их можно выбрасывать непосредственно или использовать как базовые классы для пользовательских типов исключений.

  • System.ArgumentException — выбрасывается при вызове функции с неправильным аргументом.
  • System.ArgumentNullException — производный от ArgumentException класс, выбрасывается если один из аргументов функции неожиданно равен null.
  • System.ArgumentOutOfRangeException — производный от ArgumentException класс, выбрасывается когда аргумент функции имеет слишком большое или слишком маленькое значение для данного типа (обычно касается числовых типов). Например, такое исключение будет выброшено если попытаться передать отрицательное число в функцию, которая ожидает только положительные числа.
  • System.InvalidOperationException — выбрасывается когда состояние объекта является неподходящим для нормального выполнения метода, например, при попытке прочесть не открытый файл.
  • System.NotSupportedException — выбрасывается, когда запрошенный функционал не поддерживается, например, если попытаться вызвать метод Add для коллекции доступной только для чтения (свойство коллекции IsReadOnly возвращает true).
  • System.NotImplementedException — выбрасывается, когда запрошенный функционал еще не реализован.
  • System.ObjectDisposedException — выбрасывается при попытке вызвать метод объекта, который уже был уничтожен (disposed).

Директивы препроцессора

Директивы препроцессора снабжают компилятор дополнительной информацией об областях кода. Самые распространенные директивы препроцессора — условные директивы, позволяющие включить или исключить области кода из компиляции.

#define DEBUG

class MyClass

{

  int x;

  void Foo()

  {

      # if DEBUG

      Console.WriteLine («Testing: x = {0}», x);

      # endif

  }

}

В этом классе инструкции в методе Foo скомпилируются если определен символ DEBUG, а если его удалить — инструкции не скомпилируются. Символы препроцессора могут быть определены в исходном коде (как в примере), а могут быть переданы компилятору в командной строке с помощью параметра /define:symbol.

С директивами #if и #elif можно использовать операторы ||, && и ! с несколькими символами:

Директивы #error и #warning предотвращают некорректное использование условных директив, заставляя компилятор генерировать предупреждение или ошибку при передаче неверного набора символов.

Директивы препроцессора схожи с условными конструкциями и статическими переменными, однако дают возможности, недоступные для последних:

  • условное включение атрибута
  • изменение типа, объявляемого для переменной
  • переключение между разными пространствами имен или псевдонимами типа в директиве using:

    using TestType =

      #if V2

          MyCompany.Widgets.GadgetV2;

      #else

          MyCompany.Widgets.Gadget;

      #endif

  • создавать новые версии кода и быстро переключаться между ними при компиляции
  • создавать библиотеки, компилируемые для разных версий .NET Framework

Полный список директив препроцессора:

  • #define symbol — определяет символ
  • #undef symbol — удаляет символ
  • #if symbol [оператор symbol2]... — условная компиляция; допустимые операторы ==, !=, && и ||
  • #else — выполняет код после #endif
  • #elif symbol [оператор symbol2] — объединяет #else и #if
  • #endif — конец условных директив
  • #warning text — текст предупреждения, которое появится в выдаче компилятора
  • #error text — текст ошибки, которая появится в выдаче компилятора
  • #line [число["файл"] | hidden]число указывает номер строки в исходном коде; файл — имя файла, которое появится в выдаче компилятора; hidden — дает указание дебагеру пропустить код от этой точки до следующей директивы #line
  • #region name — отмечает начало области
  • #endregion — отмечает конец области
  • #pragma warning

Pragma Warning

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

public class Foo

{

  static void Main() { }

  #pragma warning disable 414

  static string Message = «Hello»;

  #pragma warning restore 414

}

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

Если не указывать номер директива #pragma warning отменит или восстановит вывод всех предупреждений.

Если скомпилировать программу с параметром /warnaserror, то все не отмененные директивой #pragma warning предупреждения будут расцениваться компилятором как ошибки.

Атрибут Conditional

Атрибут Conditional указывает компилятору на необходимость игнорировать все обращения к определенному классу или методу, если заданный символ не был определен:

[Conditional («LOGGINGMODE»)]

static void LogStatus (string msg)

{

  ...

}

Это равносильно тому, что каждый вызов метода будет окружен условными директивами:

#if LOGGINGMODE

LogStatus («Message Headers: « + GetMsgHeaders());

#endif

Классы Debug и Trace

Статические классы Debug и Trace предлагают базовые возможности логирования. Оба класса схожи, отличие заключается в их назанчении. Класс Debug предназначен для отладочных сборок, класс Trace — для отладочных и финальных. В связи с этим все методы класса Debug определены с атрибутом [Conditional("DEBUG")], а методы класса Trace — с атрибутом [Conditional("TRACE")]. Это значит, что все обращения к Debug и Trace будут подавляться компилятором, пока не определен символ DEBUG или TRACE.

Класс Debug и Trace определяют методы Write, WriteLine и WriteIf. По умолчанию они отправляют сообщения в окно вывода отладчика:

Debug.Write («Data»);

Debug.WriteLine (23 * 34);

int x = 5, y = 3;

Debug.WriteIf (x > y, «x is greater than y»);

Класс Trace также содержит методы TraceInformation, TraceWarning и TraceError. Их действия зависят от зарегистрированных прослушивателей.

TraceListener

Классы Debug и Trace имеют свойство Listeners, которое представляет собой статическую коллекцию экземпляров TraceListener. Они отвечают за обработку данных, возвращаемых методами Write, Fail и Trace.

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

  • при подключении к отладчику (например, Visual Studio) сообщения записываются в окно вывода отладчика, во всех остальных случаях сообщения игнорируются
  • при вызове метода Fail отображается диалоговое окно, запрашивающее у пользователя дальнейшие действия: продолжить, прервать или повторить отладку (независимо от того, подключен ли отладчик)

Это поведение можно изменить или дополнить, удалив (на обязательно) стандартный прослушиватель и/или добавив один или более собственных прослушивателей.

Прослушиваетли трассировки можно написать с нуля (создав производный класс от TraceListener) или воспользоваться готовыми классами:

  • TextWriterTraceListener записывает в Stream или TextWriter или добавляет в файл; имеет четыре подкласса: ConsoleTraceListener, DelimitedListTraceListener, XmlWriterTraceListener и EventSchemaTraceListener
  • EventLogTraceListener записывает в журнал событий Windows
  • EventProviderTraceListener записывает в систему трассировки событий Windows (Event Tracing for Windows — ETW)
  • WebPageTraceListener выводит на веб-страницу ASP.NET

Ни один из этих прослушивателе не отображает диалоговое окно при вызове Fail, это делает только DefaultTraceListener.

// Удалить стандартный прослушиватель, очистив коллекцию прослушивателей:

Trace.Listeners.Clear();

// Добавить средство записи в файл trace.txt:

Trace.Listeners.Add (new TextWriterTraceListener («trace.txt»));

// Добавит средство записи в консоль:

System.IO.TextWriter tw = Console.Out;

Trace.Listeners.Add (new TextWriterTraceListener (tw));

// Добавить средство записи в журнал событий Windows:

if (!EventLog.SourceExists («DemoApp»))

  EventLog.CreateEventSource («DemoApp», «Application»);

Trace.Listeners.Add (new EventLogTraceListener («DemoApp»));

В случае журнала событий Windows сообщения, отправляемые с помощью Write, Fail или Assert, записываются как сведения, а сообщения методов TraceWarning и TraceError записываются как предупреждения или ошибки.

Каждый экземпляр TraceListener имеет свойство Filter и TraceFilter, с помощью которых можно управлять, будет ли сообщение записано в этот прослушиватель. Для этого необходимо создать экземпляр классов EventTypeFilter или SourceFilter (производных от TraceFilter) или создать свой класс, наследующий от TraceFilter и переопределить в нем метод ShouldTrace.

В TraceListener также определены свойства IndentLevel и IndentSize для управления отступами и свойство TraceOutputOptions для записи дополнительных данных:

TextWriterTraceListener tl = new TextWriterTraceListener (Console.Out);

tl.TraceOutputOptions = TraceOptions.DateTime | TraceOptions.Callstack;

// Это применяется при использовании метода Trace:

Trace.TraceWarning («Orange alert»);

DiagTest.vshost.exe Warning: 0 : Orange alert

DateTime=20070308T05:57:13.6250000Z

Callstack= at System.Environment.GetStackTrace(Exception e, Boolean

needFileInfo)

at System.Environment.get_StackTrace() at ...

Прослушиватели, которые записывают данные в поток, кэшируются. По этой причине данные не появляются в потоке немедленно, а также поток перед завершением приложения должен быть закрыт, или хотя бы сброшен, чтоб не потерять данные в кэше. Для этой цели классы Trace и Debug содержат статические методы Close и Flush, которые вызывают Close и Flush во всех прослушивателях (а они в свою очередь закрывают или сбрасывают все потоки). Метод Close вызывает метод Flush, закрывает файловые дескрипторы и предотвращает дальнейшую запись.

Классы Trace и Debug также определяют свойство AutoFlush, которое если равно true вызывает Flush после каждого сообщения.

Fail и Assert

Классы Debug и Trace содержат методы Fail и Assert.

Метод Fail отправляет сообщения каждому TraceListener:

Debug.Fail («File data.txt does not exist!»);

Метод Assert вызывает Fail если аргумент типа bool равен false. Это называется созданием утверждения и указывает на ошибку, если оно нарушено. Можно также создать необязательное сообщение об ошибке:

Debug.Assert (File.Exists («data.txt»), «File data.txt does not exist!»);

var result = ...

Debug.Assert (result != null);

Методы Write, Fail и Assert также могут принимать категорию в виде строки ,которая может быть использована при обработке вывода.

В этой статье (перевод [1]) раскрываются следующие вопросы, касающиеся обработки исключений (Exception):

• Базовые понятия исключений C# (с примерами)
• Общие исключения .NET
• Как создать свои собственные пользовательские типы исключений
• Как найти скрытые исключения .NET
• Как лучше всего документировать и отслеживать исключения C#

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

Исключения (Exceptions) это тип ошибки, которая происходит при выполнении приложения. Ошибки обычно означают появление неожиданных проблем. Тогда как исключения, обработка которых организована в коде, являются ожидаемыми, они происходят в коде приложений по различным причинам.

Приложения используют логику обработки исключений (exception handling) для явной поддержки кодом каких-то неординарных событий. Причины исключений могут быть самые разные — от печально известного NullReferenceException до таймаута обращения к базе данных.

Анатомия исключений C#. Исключения позволяют передать управление из одной части кода в другую часть. Когда срабатывает/выбрасывается исключение (exception thrown), текущий поток выполнения кода секции try прерывается, и запускается выполнение секции catch. Обработка исключений C# осуществляется следующими ключевыми словами: try, catch, finally и throw.

try — блок try инкапсулирует проверяемый на исключение регион кода. Если любая строка кода в этом блоке вызывает срабатывание исключения, то исключение будет обработано соответствующим блоком catch.
catch — когда происходит исключение, запускается блок кода catch. Это то место, где Вы можете обработать исключения и предпринять адекватные для него действия, например записать событие ошибки в лог, прервать работу программы, или может просто игнорировать исключение (когда блок catch пустой).
finally — блок finally позволяет Вам выполнить какой-то определенный код приложения, если исключение сработало, или если оно не сработало. Например, освобождение объекта из памяти, который должен быть освобожден. Часто блок finally опускается, когда обработка исключения подразумевает нормальное дальнейшее выполнение программы — потому блок finally может быть просто заменен обычным кодом, который следует за блоками try и catch.
throw — ключевое слово throw используется для реального создания нового исключения, в результате чего выполнение кода попадет в соответствующие блоки catch и finally.

Пример 1: базовый блок «try catch finally»

На языке C# ключевые слова try и catch используются для определения блока проверяемого кода (блок try catch). Блок try catch помещается вокруг кода, который потенциально может выбросить исключение. Если произошло исключение, то этот блок try catch обработает исключение, гарантируя тем самым, что приложение не выдаст ошибку необработанного исключения (unhandled exception) [5], ошибки пользователя, и эта ошибка не разрушит процесс выполнения приложения.

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

WebClient wc = null;
try
{
   wc = new WebClient();   // загрузка web-страницы
   var resultData = wc.DownloadString("http://google.com");
}
catch (ArgumentNullException ex)
{
   // код, специфичный для исключения ArgumentNullException
}
catch (WebException ex)
{
   // код, специфичный для исключения WebException
}
catch (Exception ex)
{
   // код, специфичный для любого другого типа исключения
}
finally
{
   // В любом случае, сработает это исключение или нет,
   // произойдет освобождение экземпляра класса WebClient.
   wc.Dispose();
}

Ваш код обработки исключения на C# может применить несколько операторов catch, предназначенных для разных типов исключений. Это может быть очень полезным — в зависимости от того, что делает код. В предыдущем примере ArgumentNullException возникнет только тогда, когда переданный URL сайта окажется null. WebException происходит из-за широкого массива разных проблем. Перехват определенных типов исключений может помочь в их обработке.

Пример 2: фильтры исключений

Одна из новых функций C# версии 6 был ввод фильтров исключений (exception filters). Они дают Вам еще больше контроля над блоками catch и дальнейшей обработкой определенных исключений. Это помогает точно подстроить код под то, как Вы обрабатываете исключения, и как Вы хотите их обработать.

До C# 6 Вы должны были поймать все типы WebException и обработать их. Теперь Вы можете принять решение только обработать их в определенных сценариях и позволить другому пузырю сценариев до кода который названный этим методом. Вот измененный пример с фильтрами:

WebClient wc = null;
try
{
   wc = new WebClient();   // загрузка web-страницы
   var resultData = wc.DownloadString("http://google.com");
}
catch (WebException ex) when (ex.Status == WebExceptionStatus.ProtocolError)
{
   // код, специально предназначенный для обработки ошибки ProtocolError
}
catch (WebException ex) when ((ex.Response as HttpWebResponse).StatusCode
                              == HttpStatusCode.NotFound)
{
   // код, специально предназначенный для обработки ошибки NotFound
}
catch (WebException ex) when ((ex.Response as HttpWebResponse).StatusCode
                              == HttpStatusCode.InternalServerError)
{
   // код, специально предназначенный для обработки InternalServerError
}
finally
{
   // Этот код будет вызван в любом случае - сработало исключение, или нет.
   wc.Dispose();
}

[Общие исключения .NET]

Правильная обработка исключения критична для всего кода приложения. Имеется несколько часто используемых стандартных исключений (Common .NET Exceptions). Чаще всего стоит бояться исключения обращения по не инициализированной ссылке (null reference exception). Ниже приведен список общих ошибок, которые Вы будете наблюдать регулярно.

System.NullReferenceException — наиболее общее исключение, связанное с вызовом метода, когда его объект не инициирован.
System.IndexOutOfRangeException — произошла попытка доступа к не существующему элементу массива.
System.IO.IOException — используется в связи с файловыми операциями ввода/вывода (file I/O).
System.Net.WebException — обычно выбрасывается при любых ошибках, связанных с вызовами протокола HTTP.
System.Data.SqlClient.SqlException — различные типы исключений сервера SQL.
System.StackOverflowException — если метод рекурсивно вызывает сам себя, то Вы можете получить это исключение.
System.OutOfMemoryException — если приложение столкнулось с недостатком памяти.
System.InvalidCastException — если Вы попытались сделать приведение типа объекта (cast) к несовместимому типу.
System.InvalidOperationException — общее стандартное исключения в некоторых библиотеках.
System.ObjectDisposedException — попытка использовать объект, который уже освобожден.

[Как создать свои собственные пользовательские типы исключений]

Исключения C# определены как классы, точно так же, как и любой другой объект C#. Все исключения наследуются от базового класса System.Exception. Есть много общих исключений (common exceptions), которые Вы можете использовать в своем собственном коде. Обычно разработчики используют стандартный объект ApplicationException или Exception для выбрасывания пользовательских исключений (custom exceptions). Вы также можете создать свой собственный тип исключения.

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

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

Преимущества пользовательского типа исключений C#:

• Вызов кода может осуществлять пользовательскую обработку Custom Exception Type
• Возможность пользовательского мониторинга вокруг этого Custom Exception Type

Пример Custom Exception Type:

public void DoBilling(int clientID)
{
   Client client = _clientDataAccessObject.GetById(clientID);
 
   if (client == null)
   {
      throw new ClientBillingException(string.Format("Unable to find a client by id {0}",
                                                      clientID));
   }
}
 
public class ClientBillingException : Exception
{
   public ClientBillingException(string message)
      : base(message)
   {
   }
}

[Как найти скрытые исключения .NET]

Что такое First Chance Exceptions (исключения первого шанса)? Нормальная ситуация для большого количества исключений быть выброшенными, пойманными и затем проигнорированными. Внутренний код .NET Framework даже выбрасывает некоторые исключения, которые отбрасываются. Одна из функций C# это так называемые исключения первого шанса (first chance exceptions). Это позволяет Вам увидеть каждое выбрасываемое исключение .NET Exception по отдельности.

Код, наподобие приведенного ниже, очень часто встречается в приложениях. Этот код может выбросить (throw) тысячи исключений в минуту, и никто никогда про это бы не узнал. Этот код из приложения, который показывал серьезные проблемы производительности из-за плохой обработки исключений. Исключения произойдут, если reader равен null, columnName равен null, columnName не существует в результатах, значение столбца было null, или если value неправильная для DateTime. Настоящее минное поле ошибок.

public DateTime.GetDate(SqlDataReader reader, string columnName)
{
   DateTime.value = null;
   try
   {
      value = DateTime.Parse(reader[columnName].ToString());
   }
   catch
   {
   }
   return value;
}

Как в Visual Studio разрешить First Chance Exceptions. Когда запускаете приложение в отладчике Visual Studio, Вы можете установить Visual Studio останавливаться (break) в любой момент, когда выбрасывается C# Exception. Это может помочь найти исключения в своем коде, о которых Вы не знали, что они существуют.

Чтобы получить доступ к настройкам исключений, перейдите в меню Debug -> Windows -> Exception Settings (Отладка -> Исключения…, это меню доступно при активной сессии отладчика). Под «Common Language Runtime Exceptions» Вы можете выбрать типы исключений, на которых отладчик должен автоматически поставить точку останова. Хорошей мыслью будет поставить здесь везде галочки. Как только код остановится на исключении, Вы можете указать ему игнорировать этот определенный тип исключений, если хотите.

Visual Studio Exception Settings

Как просмотреть все исключения с префиксом. Бесплатный Stackify .NET profiler [6] также может показать все Ваши исключения. Подробнее см. статью [7]. Решение Stackify Retrace [8] (платное) для Ваших серверов также может собирать все исключения первого шанса через .NET profiler. Без каких-либо изменений в коде или конфигурации ом может автоматически собрать и показать все исключения.

Как в коде подписаться на First Chance Exceptions. Среда .NET Framework предоставляет способ подписаться на событие, чтобы запускать функцию обратного вызова (callback) в любой момент возникновения исключения. Вы можете использовать это, чтобы перехватить все исключения. Хорошей мыслью организовать потенциальную подписку на исключения, чтобы выводить информацию о них в окно отладки. Это дало бы некоторое отображение текущей ситуации в приложении без загромождения информацией Ваших лог-файлов. Обычно подписка делается один раз при старте приложения — в методе Main() консольного приложения или в коде запуска (startup) web-приложения ASP.NET.

AppDomain.CurrentDomain.FirstChanceException += (sender, eventArgs) =>
{
   Debug.WriteLine(eventArgs.Exception.ToString());
};

[Как лучше всего документировать и отслеживать исключения C#]

Правильная обработка исключения критична для любого приложения. Ключевой компонент — создание библиотеки записи в лог исключений. Подробнее про это см. статью «C# Logging Best Practices» [9]. Лучше всего записывать исключения с использованием библиотек NLog, Serilog или log4net. Все эти три фреймворка дают Вам возможность записывать исключения в файл. Также они позволяют отправлять Ваши логи различным другим получателям — база данных, система лога Windows (Windows Event Viewer), email, или служба мониторинга ошибок (error monitoring service). Любое исключение в приложении должно быть записано, это критично для поиска проблем в Вашем коде.

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

try
{
   // Какие-то проверяемые действия
   ...
}
catch (Exception ex)
{
   // ЭТО НАДО ЗАПИСАТЬ В ЛОГ!
   Log.Error(string.Format("Excellent description goes here about
                            the exception. Happened for client {0}",
                            _clientContext.ClientId), ex);
   throw;   // может повторно выбросить ошибку, чтобы сообщить об исключении
            // пользователю, или throw можно закомментировать, чтобы игнорировать
            // исключение.
}

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

Служба мониторинга ошибок — ключевой инструмент для любой команды разработки. Она позволяет централизованно собирать все Ваши исключения. Решение [8] предоставляет для этого следующие возможности:

• Централизованный лог исключений
• Просмотр и поиск всех исключений по всем серверам и приложениям
• Уникально идентифицировать каждое исключение
• Принимать оповещения на email о возникновении новых исключений или в случае слишком частого появления ошибок

[Ссылки]

1. C# Exception Handling Best Practices site:stackify.com.
2. try-catch-finally (Справочник по C#) site:docs.microsoft.com.
3. Операторы throw и finally site:professorweb.ru.
4. Обработка исключений site:metanit.com.
5. What is an Unhandled Exception and How to Find Them site:stackify.com.
6. Prefix is a Must-Have Tool for Code Diagnostics? site:stackify.com.
7. Finding Hidden Exceptions in Your Application with Prefix site:stackify.com.
8. Retrace is a Game Changer for Dev Team Deployments site:stackify.com.
9. How to Take Logging to the Next Level With Improved .NET Logging Best Practices site:stackify.com.

Обработка ошибок

Данное руководство устарело. Актуальное руководство: Руководство по ASP.NET Core 7

Последнее обновление: 06.11.2019

Ошибки в приложении можно условно разделить на два типа: исключения, которые возникают в процессе выполнения кода (например, деление на 0), и
стандартные ошибки протокола HTTP (например, ошибка 404).

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

UseDeveloperExceptionPage

Если мы создаем проект ASP.NET Core, например, по типу Empty (да и в других типах проектов), то в классе Startup мы можем найти в начале метода Configure() следующие строки:

if (env.IsDevelopment())
{
	app.UseDeveloperExceptionPage();
}

Если приложение находится в состоянии разработки, то с помощью middleware app.UseDeveloperExceptionPage() приложение перехватывает исключения и
выводит информацию о них разработчику.

Например, изменим класс Startup следующим образом:

public class Startup
{
	public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
	{
		if (env.IsDevelopment())
		{
			app.UseDeveloperExceptionPage();
		}
		app.Run(async (context) =>
		{
			int x = 0;
			int y = 8 / x;
			await context.Response.WriteAsync($"Result = {y}");
		});
	}
}

В middleware app.Run симулируется генерация исключения при делении ноль. И если мы запустим проект, то в браузере мы увидим
информацию об исключении:

Обработка исключений в ASP.NET Core

Этой информации достаточно, чтобы определить где именно в коде произошло исключение.

Теперь посмотрим, как все это будет выглядеть для простого пользователя. Для этого изменим метод Configure:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
	env.EnvironmentName = "Production";
	if (env.IsDevelopment())
	{
		app.UseDeveloperExceptionPage();
	}
	app.Run(async (context) =>
	{
		int x = 0;
		int y = 8 / x;
		await context.Response.WriteAsync($"Result = {y}");
	});
}

Выражение env.EnvironmentName = "Production"; устанавливает режим развертывания вместо режима разработки. В этом случае выражение if (env.IsDevelopment()) будет возвращать false, и мы увидим в браузере что-то наподобие «HTTP ERROR 500»

HTTP ERROR 500 в ASP.NET Core

UseExceptionHandler

Это не самая лучшая ситуация, и нередко все-таки возникает необходимость дать пользователям некоторую информацию о том, что же все-таки произошло. Либо потребуется как-то обработать данную ситуацию.
Для этих целей можно использовать еще один встроенный middleware в виде метода UseExceptionHandler(). Он перенаправляет
при возникновении исключения на некоторый адрес и позволяет обработать исключение. Например, изменим метод Configure следующим образом:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
	env.EnvironmentName = "Production";
	
	if (env.IsDevelopment())
	{
		app.UseDeveloperExceptionPage();
	}
	else
	{
		app.UseExceptionHandler("/error");
	}
	
	app.Map("/error", ap => ap.Run(async context =>
	{
		await context.Response.WriteAsync("DivideByZeroException occured!");
	}));
	
	app.Run(async (context) =>
	{
		int x = 0;
		int y = 8 / x;
		await context.Response.WriteAsync($"Result = {y}");
	});
}

Метод app.UseExceptionHandler("/error"); перенаправляет при возникновении ошибки на адрес «/error».

Для обработки пути по определенному адресу здесь использовался метод app.Map(). В итоге при возникновении исключения будет срабатывать делегат
из метода app.Map.

Error Handling in ASP.NET Core

Следует учитывать, что оба middleware — app.UseDeveloperExceptionPage() и app.UseExceptionHandler()
следует помещать ближе к началу конвейера middleware.

Обработка ошибок HTTP

В отличие от исключений стандартный функционал проекта ASP.NET Core почти никак не обрабатывает ошибки HTTP, например, в случае если ресурс не найден.
При обращении к несуществующему ресурсу мы увидим в браузере пустую страницу, и только через консоль веб-браузера мы сможем увидеть статусный код.
Но с помощью компонента StatusCodePagesMiddleware можно добавить в проект отправку информации о статусном коде.
Для этого добавим в метод Configure() класса Startup вызов app.UseStatusCodePages():

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
	if (env.IsDevelopment())
	{
		app.UseDeveloperExceptionPage();
	}
	
	// обработка ошибок HTTP
	app.UseStatusCodePages();
	
	app.Map("/hello", ap => ap.Run(async (context) =>
	{
		await context.Response.WriteAsync($"Hello ASP.NET Core");
	}));
}

Здесь мы можем обращаться только по адресу «/hello». При обращении ко всем остальным адресам браузер отобразит базовую информацию об ошибке:

UseStatusCodePages в ASP.NET Core

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

app.UseStatusCodePages("text/plain", "Error. Status code : {0}");

В качестве первого параметра указывается MIME-тип ответа, а в качестве второго — собственно то сообщение, которое увидит пользователь. В сообщение мы можем
передать код ошибки через плейсхолдер «{0}».

Вместо метода app.UseStatusCodePages() мы также можем использовать еще пару других, которые также обрабатываю ошибки HTTP.

С помощью метода app.UseStatusCodePagesWithRedirects() можно выполнить переадресацию на определенный метод, который непосредственно обработает статусный код:

app.UseStatusCodePagesWithRedirects("/error?code={0}");

Здесь будет идти перенаправление по адресу «/error?code={0}». В качестве параметра через плейсхолдер «{0}» будет передаваться статусный код
ошибки.

Но теперь при обращении к несуществующему ресурсу клиент получит статусный код 302 / Found. То есть формально несуществующий ресурс будет существовать, просто статусный код 302
будет указывать, что ресурс перемещен на другое место — по пути «/error/404».

Подобное поведение может быть неудобно, особенно с точки зрения поисковой индексации, и в этом случае мы можем применить другой метод
app.UseStatusCodePagesWithReExecute():

app.UseStatusCodePagesWithReExecute("/error", "?code={0}");

Первый параметр метода указывает на путь перенаправления, а второй задает параметры строки запроса, которые будут передаваться при перенаправлении.
Вместо плейсхолдера {0} опять же будет передаваться статусный код ошибки. Формально мы получим тот же ответ, так как так же будет идти перенаправление на путь «/error?code=404». Но теперь браузер получит оригинальный статусный код 404.

Пример использования:

public void Configure(IApplicationBuilder app)
{
	// обработка ошибок HTTP
	app.UseStatusCodePagesWithReExecute("/error", "?code={0}");

	app.Map("/error", ap => ap.Run(async context =>
	{
		await context.Response.WriteAsync($"Err: {context.Request.Query["code"]}");
	}));

	app.Map("/hello", ap => ap.Run(async (context) =>
	{
		await context.Response.WriteAsync($"Hello ASP.NET Core");
	}));
}

Настройка обработки ошибок в web.config

Еще один способ обработки кодов ошибок представляет собой определение и настройка в файле конфигурации web.config элемента
httpErrors. Этот способ в принципе использовался и в других версиях ASP.NET.
В ASP.NET Core он также доступен, однако имеет очень ограниченное действие. В частности, мы его можем использовать только при развертывании на IIS, а также не можем использовать ряд настроек.

Итак, добавим в корень проекта новый элемент Web Configurarion File, который естественно назовем web.config:

Обработка ошибок в web.config

Изменим его следующим образом:

<?xml version="1.0" encoding="utf-8"?>
<configuration>

  <system.webServer>
	<httpErrors errorMode="Custom" existingResponse="Replace">
      <remove statusCode="404"/>
	  <remove statusCode="403"/>
      <error statusCode="404" path="404.html" responseMode="File"/>
      <error statusCode="403" path="403.html" responseMode="File"/>
	</httpErrors>
   
    <handlers>
      <add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModule" resourceType="Unspecified"/>
    </handlers>
    <aspNetCore processPath="%LAUNCHER_PATH%" arguments="%LAUNCHER_ARGS%" stdoutLogEnabled="false" stdoutLogFile=".logsstdout" forwardWindowsAuthToken="false"/>
  </system.webServer>
</configuration>

Также для обработки ошибок добавим в корень проекта новый файл 404.html:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>Ошибка 404</title>
</head>
<body>
    <h1>Ошибка 404</h1>
    <h2>Ресурс не найден!</h2>
</body>
</html>

По аналогии можно добавить файл 403.html для ошибки 403.

Итак, элемент httpErrors имеет ряд настроек. Для тестирования настроек локально, необходимо установить атрибут errorMode="Custom".
Если тестирование необязательно, и приложение уже развернуто для использования, то можно установить значение errorMode="DetailedLocalOnly".

Значение existingResponse="Replace" позволит отобразить ошибку по оригинальному запрошенному пути без переадресации.

Внутри элемента httpErrors с помощью отдельных элементов error устанавливается обработка ошибок. Атрибут statusCode
задает статусный код, атрибут path — адрес url, который будет вызываться, а атрибут responseMode указывает, как будет обрабатываться ответ вызванному url.
Атрибут responseMode имеет значение File, что позволяет рассматривать адрес url из атрибута path как статическую страницу и использовать ее в качестве ответа

Настройки элемента httpErrors могут наследоваться с других уровней, например, от файла конфигурации machine.config. И чтобы удалить
все унаследованные настройки, применяется элемент <clear />. Чтобы удалить настройки для отдельных ошибок, применяется элемент
<remove />.

Для тестирования используем следующий класс Startup:

public class Startup
{
	public void Configure(IApplicationBuilder app)
	{
		app.Map("/hello", ap => ap.Run(async (context) =>
		{
			await context.Response.WriteAsync($"Hello ASP.NET Core");
		}));
	}
}

И после обращения к несуществующему ресурсу в приложении отобразится содержимое из файла 404.html.

Настройка обработки ошибок в web.config в ASP.NET Core

На 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, уроки по си шарп, си шарп, исключения, ошибки

Читайте также:

  • Урок 30. Условные операторы C#

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

В платформе .NET
Framework исключение — это объект, наследуемый
от класса System.Exception.
Исключение посылается из области кода,
где возникла проблема. Исключение
передается в стек до тех пор, пока его
не обработает приложение или не завершится
выполнение программы.

Исключения и традиционные методы обработки ошибок

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

Обработка исключений
без учета языка, на котором сгенерировано
или обрабатывается данное исключение.

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

Возможность
порождения исключений между процессам
и даже между компьютерами.

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

Поскольку потоки
исключений очень просто проходят по
блокам как управляемых, так и неуправляемых
кодов, среда выполнения может создавать
и перехватывать исключения и в управляемом,
и в неуправляемом коде. Неуправляемый
код может включать и исключения SEH в
стиле С++, и HRESULTS из COM.

Управление исключениями средой выполнения

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

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

Таблица сведений
об исключении отображает четыре типа
обработчиков исключений для защищенных
блоков.

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

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

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

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

Каждый язык
реализует такие обработчики исключений
в соответствии с его спецификациями.
Например, Visual Basic предоставляет доступ
к обработчику с пользовательской
фильтрацией посредством сравнения
переменных (с использованием ключевого
слова When) в операторе catch; C# не реализует
обработчик с пользовательской фильтрацией.

При возникновении
исключения среда выполнения начинает
выполнение процесса, состоящего из двух
шагов:

Среда выполнения
производит поиск массива для первого
защищенного блока, в котором:

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

содержится
обработчик исключения или фильтр для
обработки этого исключения.

Если массив найден,
среда выполнения создает объект
Exception,
описывающий данное исключение. Затем
среда выполнения выполняет все операторы
finally или сбойные операторы, находящиеся
между оператором, в котором возникло
исключение, и оператором обработки
этого исключения. Обратите внимание,
что важен порядок обработчиков исключений
— первым определяется внутренний
обработчик. Обратите внимание, что
обработчики исключений могут получать
доступ к локальным переменным и локальной
памяти программы, перехватывающей
исключение, но любые промежуточные
значения на момент инициации исключения
теряются.

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

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]

  • #
  • #
  • #
  • #
  • #
  • #
  • #
  • #
  • #
  • #
  • #

Понравилась статья? Поделить с друзьями:
  • Net framework код ошибки 0x80070002
  • Net framework 3 5 код ошибки 0x800f081f
  • Net broadcast event ошибка при выключении
  • Net antennae altium ошибка
  • Nesting error foxpro ошибка