Up to now, whenever I wanted to show an exception thrown from my code I used:
try
{
// Code that may throw different exceptions
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
}
I used the above code mainly for debugging reasons, in order to see the exact type of exception and the according reason the exception was thrown.
In a project I am creating now, I use several try-catch
clauses and I would like to display a popup message in case of an exception, to make it more «user friendly». By «user friendly», I mean a message that would hide phrases like Null Reference Exception or Argument Out Of Range Exception that are currently displayed with the above code.
However I still want to see relevant info with the type of exception that created the message.
Is there a way to format the displayed output of thrown exceptions according to previous needs?
Carsten
11.2k7 gold badges39 silver badges61 bronze badges
asked Apr 22, 2013 at 10:55
4
You can use .Message
, however I wouldn’t recommend just catching Exception
directly. Try catching multiple exceptions or explicitly state the exception and tailor the error message to the Exception type.
try
{
// Operations
}
catch (ArgumentOutOfRangeException ex)
{
MessageBox.Show("The argument is out of range, please specify a valid argument");
}
Catching Exception
is rather generic and can be deemed bad practice, as it maybe hiding bugs in your application.
You can also check the exception type and handle it accordingly by checking the Exception type:
try
{
}
catch (Exception e)
{
if (e is ArgumentOutOfRangeException)
{
MessageBox.Show("Argument is out of range");
}
else if (e is FormatException)
{
MessageBox.Show("Format Exception");
}
else
{
throw;
}
}
Which would show a message box to the user if the Exception is an ArgumentOutOfRange or FormatException, otherwise it will rethrow the Exception (And keep the original stack trace).
answered Apr 22, 2013 at 11:00
DarrenDarren
68.5k24 gold badges136 silver badges144 bronze badges
5
try
{
/////Code that may throws several types of Exceptions
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
Use above code.
Can also show custom error message as:
try
{
/////Code that may throws several types of Exceptions
}
catch (Exception ex)
{
MessageBox.Show("Custom Error Text "+ex.Message);
}
Additional :
For difference between ex.toString() and ex.Message follow:
Exception.Message vs Exception.ToString()
All The details with example:
http://www.dotnetperls.com/exception
answered Apr 22, 2013 at 10:57
FreelancerFreelancer
8,9837 gold badges42 silver badges81 bronze badges
1
Exception.Message
provides a more (but not entirely) user-friendly message than Exception.ToString()
. Consider this contrived example:
try
{
throw new InvalidOperationException();
}
catch(InvalidOperationException ex)
{
Console.WriteLine(ex.ToString());
}
Although Message
yields a simpler message than ToString()
the message displayed will still not mean much to the user. It won’t take you much effort at all to manually swallow exceptions and display a custom message to the user that will assist them in remedying this issue.
try
{
using (StreamReader reader = new StreamReader("fff")){}
}
catch(ArgumentException argumentEx)
{
Console.WriteLine("The path that you specified was invalid");
Debug.Print(argumentEx.Message);
}
catch (FileNotFoundException fileNotFoundEx)
{
Console.WriteLine("The program could not find the specified path");
Debug.Print(fileNotFoundEx.Message);
}
You can even use Debug.Print
to output text to the immediate window or output window (depending on your VS preferences) for debugging purposes.
answered Apr 22, 2013 at 10:56
User 12345678User 12345678
7,6942 gold badges28 silver badges46 bronze badges
You can use Exception.Message property to get a message that describes the current exception.
catch (Exception ex)
{
MessageBox.Show(ex.Messagge());
}
answered Apr 22, 2013 at 10:57
ArshadArshad
9,6746 gold badges37 silver badges61 bronze badges
try this code :
try
{
// Code that may throw different exceptions
}
catch (Exception exp)
{
MessageBox.Show(exp.Message());
}
answered Apr 12, 2014 at 22:27
0
The trick is using the Message method of the exception:
catch (Exception ex)
{
MessageBox.Show(this, ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
Alenros
8187 silver badges23 bronze badges
answered Jan 21, 2021 at 6:18
Время на прочтение
4 мин
Количество просмотров 13K
Всё началось с безобидного пролистывания GCC расширений для C. Мой глаз зацепился за вложенные функции. Оказывается, в C можно определять функции внутри функций:
int main() {
void foo(int a) {
printf("%dn", a);
}
for(int i = 0; i < 10; i ++)
foo(i);
return 0;
}
Более того, во вложенных функциях можно менять переменные из внешней функции и переходить по меткам из неё, но для этого необходимо, чтобы переменные были объявлены до вложенной функции, а метки явно указаны через __label__
int main() {
__label__ end;
int i = 1;
void ret() {
goto end;
}
void inc() {
i ++;
}
while(1) {
if(i > 10)
ret();
printf("%dn", i);
inc();
}
end:
printf("Donen");
return 0;
}
Документация говорит, что обе внутренние функции валидны, пока валидны все переменные и мы не вышли из области внешней функции, то есть эти внутренние функции можно передавать как callback-и.
Приступим к написанию try-catch. Определим вспомогательные типы данных:
// Данными, как и выкинутой ошибкой может быть что угодно
typedef void *data_t;
typedef void *err_t;
// Определяем функцию для выкидывания ошибок
typedef void (*throw_t)(err_t);
// try и catch. Они тоже будут функциями
typedef data_t (*try_t)(data_t, throw_t);
typedef data_t (*catch_t)(data_t, err_t);
Подготовка завершена, напишем основную функцию. К сожалению на хабре нельзя выбрать отдельно язык C, поэтому будем писать try_
, catch_
, throw_
чтобы их подсвечивало как функции, а не как ключевые слова C++
data_t try_catch(try_t try_, catch_t catch_, data_t data) {
__label__ fail;
err_t err;
// Объявляем функцию выбрасывания ошибки
void throw_(err_t e) {
err = e;
goto fail;
}
// Передаём в try данные и callback для ошибки
return try_(data, throw_);
fail:
// Если есть catch, передаём данные, над которыми
// работал try и ошибку, которую он выбросил
if(catch_ != NULL)
return catch_(data, err);
// Если нет catch, возвращаем пустой указатель
return NULL;
}
Напишем тестовую функцию взятия квадратного корня, с ошибкой в случае отрицательного числа
data_t try_sqrt(data_t ptr, throw_t throw_) {
float *arg = (float *)ptr;
if(*arg < 0)
throw_("Error, negative numbern");
// Выделяем кусок памяти для результата
float *res = malloc(sizeof(float));
*res = sqrt(*arg);
return res;
}
data_t catch_sqrt(data_t ptr, err_t err) {
// Если возникла ошибка, печатает её и ничего не возвращаем
fputs(err, stderr);
return NULL;
}
Добавляем функцию main, посчитаем в ней корень от 1 и от -1
int main() {
printf("------- sqrt(1) --------n");
float a = 1;
float *ptr = (float *) try_catch(try_sqrt, catch_sqrt, &a);
if(ptr != NULL) {
printf("Result of sqrt is: %fn", *ptr);
// Не забываем освободить выделенную память
free(ptr);
} else
printf("An error occuredn");
printf("------- sqrt(-1) -------n");
a = -1;
ptr = (float *)try_catch(try_sqrt, catch_sqrt, &a);
if(ptr != NULL) {
printf("Result of sqrt is: %fn", *ptr);
// Аналогично
free(ptr);
} else
printf("An error occuredn");
return 0;
}
И, как и ожидалось, получаем
------- sqrt(1) --------
Result of sqrt is: 1.000000
------- sqrt(-1) -------
Error, negative number
An error occured
Try-catch готов, господа.
На этом статью можно было бы и закончить, но тут внимательный читатель заметит, что функция throw
остаётся валидной в блоке catch
. Можно вызвать её и там, и тогда мы уйдём в рекурсию. Заметим также, что функция throw
, это не обычная функция, она noreturn
и разворачивает стек, поэтому, даже если вызвать её в catch
пару сотен раз, на стеке будет только последний вызов. Мы получаем хвостовую оптимизацию рекурсии.
Попробуем посчитать факториал на нашем try-catch. Для этого передадим указатель на функцию throw
в функцию catch
. Сделаем это через структуру, в которой также будет лежать аккумулятор вычислений.
struct args {
uint64_t acc;
throw_t throw_;
};
В функции try
инициализируем поле throw
у структуры, и заводим переменную num
для текущего шага рекурсии.
data_t try_(data_t ptr, throw_t throw_) {
struct args *args = ptr;
// Записываем функцию в структуру, чтобы catch мог её pf,hfnm
args->throw_ = throw_;
// Заводим переменную для хранения текущего шага рекурсии
uint64_t *num = malloc(sizeof(uint64_t));
// Изначально в acc лежит начальное число, в нашем случае 10
*num = args->acc;
// Уменьшаем число
(*num) --;
// Уходим в рекурсию
throw_(num);
}
В функции catch будем принимать структуру и указатель на num, а дальше действуем как в обычном рекурсивном факториале.
data_t catch_(data_t ptr, err_t err) {
struct args *args = ptr;
// В err на самом деле лежит num
uint64_t *num = err;
// Печатаем num, будем отслеживать рекурсию
printf("current_num: %"PRIu64"n", *num);
if(*num > 0) {
args->acc *= *num;
(*num) --;
// Рекурсивный вызов
args->throw_(num);
}
// Конец рекурсии
// Не забываем осовободить выделенную память
free(num);
// Выводим результат
printf("acc is: %"PRIu64"n", args->acc);
return &args->acc;
}
int main() {
struct args args = { .acc = 10 };
try_catch(try_, catch_, &args);
return 0;
}
Вызываем, и получаем, как и ожидалось:
current_num: 9
current_num: 8
current_num: 7
current_num: 6
current_num: 5
current_num: 4
current_num: 3
current_num: 2
current_num: 1
current_num: 0
acc is: 3628800
main.c
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <inttypes.h>
#include <stdnoreturn.h>
typedef void *err_t;
typedef void *data_t;
typedef void (*throw_t)(err_t);
typedef data_t (*try_t)(data_t, throw_t);
typedef data_t (*catch_t)(data_t, err_t);
data_t try_catch(try_t try, catch_t catch, data_t data) {
__label__ fail;
err_t err;
void throw(err_t e) {
err = e;
goto fail;
}
return try(data, throw);
fail:
if(catch != NULL)
return catch(data, err);
return NULL;
}
struct args {
uint64_t acc;
throw_t throw_;
};
data_t try_(data_t ptr, throw_t throw_) {
struct args *args = ptr;
args->throw_ = throw_;
uint64_t *num = malloc(sizeof(uint64_t));
*num = args->acc;
(*num) --;
throw_(num);
}
data_t catch_(data_t args_ptr, err_t num_ptr) {
struct args *args = args_ptr;
uint64_t *num = num_ptr;
printf("current_num: %"PRIu64"n", *num);
if(*num > 0) {
args->acc *= *num;
(*num) --;
args->throw_(num);
}
free(num);
printf("acc is: %"PRIu64"n", args->acc);
return &args->acc;
}
int main() {
struct args args = { .acc = 10 };
try_catch(try_, catch_, &args);
return 0;
}
Спасибо за внимание.
P.S. Текст попытался вычитать, но, так как русского в школе не было, могут быть ошибки. Прошу сильно не пинать и по возможности присылать всё в ЛС, постараюсь реагировать оперативно.
Содержание
- Исключения (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
записывает в журнал событий WindowsEventProviderTraceListener
записывает в систему трассировки событий 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=2007—03—08T05: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
также могут принимать категорию в виде строки ,которая может быть использована при обработке вывода.
Содержание
- Пример исключения в C#
- Блок try…catch…finally
- Перехват и обработка исключений в блоке catch
- Логические операции и обработка исключений в C#
- Итого
уважаемые посетители блога, если Вам понравилась, то, пожалуйста, помогите автору с лечением. Подробности тут.
При разработке программного обеспечения мало составить и реализовать какой-либо алгоритм, важно также предусмотреть всевозможные непредвиденные ситуации при работе вашей программы и, в случае необходимости отловить и обработать исключения, которые могут возникнуть. Например, вы решили разработать программу-клиент для работы с блогом, которая позволяет публиковать статьи, модерировать комментарии и выполнять прочую полезную работу. Как бы вы не старались сделать свое приложение работоспособным, неизбежно, при работе с программой пользователь может столкнуться с такими проблемами: сайт недоступен (например, в результате ошибки сервера 5хх), не возможно соединиться с базой данных и так далее. В любом из этих случаев, без должной обработки исключений, ваша программа будет аварийно завершать работу и пугать пользователей сообщениями об ошибках. Сегодня мы рассмотрим некоторые моменты по обработке исключений в C#.
Рассмотрим канонический пример того, когда работа с программой приводит к генерации исключения — деление на ноль. Вот такой может быть наша программа:
Console.WriteLine("Введите любое целое число и нажмите Enter"); int i = int.Parse(Console.ReadLine()); double x = 5; double y = x / i; Console.WriteLine($"{x}/{i}={y}");
Теперь запустим программу и введем число 0
. В итоге, в Visual Studio мы увидим ошибку:
Мы получили исключение типа System.DivideByZeroException
(деление на ноль) и наше приложение аварийно завершило свою работу. Кроме этого, в таком простом, казалось бы, приложении имеется ещё одна уязвимость — пользователь может ввести совсем не то, что от него требуется и вместо числа введет, например, строку. В этом случае мы, опять же, получим в Visual Studio исключение:
Получили исключение типа System.FormatException
. Чтобы избежать подобного аварийного завершения программы, всё, что нам остается — это обработать исключения и выдавать пользователю не стандартное окошко с красным крестом, а сообщение, которое позволит скорректировать работу с программой и, например, повторить ввод.
Блок try…catch…finally
Для обработки исключений в C# используется специальная конструкция — блок try...catch...finally
. Перепишем наше приложение следующим образом:
Console.WriteLine("Введите любое целое число и нажмите Enter"); try { int i = int.Parse(Console.ReadLine()); int x = 5; double y = x / i; Console.WriteLine($"{x}/{i}={y}"); } catch { Console.WriteLine("Неправильный ввод значения"); } finally { Console.WriteLine("Выполнили блок finally"); } _ = Console.ReadLine();
Теперь запустим программу и снова введем значение 0
. В результате, программа не завершит работу аварийно, а выведет в консоль сообщение. Вот вывод консоли:
Введите любое целое число и нажмите Enter
0
Неправильный ввод значения
Выполнили блок finally
Приложение так же, как и в предыдущем примере, дошло до строки
double y = x / i;
однако, вместо аварийной остановки на строке с ошибкой, программа перешла в блок catch
и вывела сообщение «Неправильный ввод значения». После того, как выполнен блок catch
, программа переходит в блок finally
, выполняет все операции в нем и завершает работу.
В конструкции try...catch...finally
обязательным является блок try
. Блоки catch
или finally
могут отсутствовать, при этом следует отметить, что, если отсутствует блок catch
, то исключение будет возбуждено и программа аварийно завершит работу. Варианты использования конструкции try...finally...catch
могут быть такими:
//БЕЗ БЛОКА FINALLY. Программа не завершается аварийно try { int i = int.Parse(Console.ReadLine()); int x = 5; double y = x / i; Console.WriteLine($"{x}/{i}={y}"); } catch { Console.WriteLine("Неправильный ввод значения"); }
или
//БЕЗ БЛОКА CATCH. Программа аварийно завершит работу try { int i = int.Parse(Console.ReadLine()); int x = 5; double y = x / i; Console.WriteLine($"{x}/{i}={y}"); } finally { Console.WriteLine("Выполнили блок finally"); }
Блок finally
обычно используется для выполнения очистки ресурсов выделенных в блоке try
. Блок finally
не выполниться в том случае, если в блоке catch
также, как и в try
возникнет какое-либо исключение.
Перехват и обработка исключений в блоке catch
В примере с блоком catch
выше всё, что мы сделали — это вывели одно сообщение о том, что пользователь ввел неверное значение. При этом, при разработке реальных приложений часто необходимо не только сообщить пользователю об исключении, но и постараться направить его на «путь истинный». Например, в нашем тестовом приложении пользователь, как мы определили может:
- ввести 0 (исключение
System.DivideByZeroException
) - ввести вместо целого числа строку (исключение
System.FormatException
) - ввести вместо целого числа число с плавающей запятой (исключение
System.FormatException
) - ввести число, превышающее максимальное значение
int
(исключениеSystem.OverflowException
)
Во всех этих случаях мы должны каким-либо образом пояснить пользователю, что он сделал не так. Для этого, перепишем наш код с блокомcatch
следующим образом:
try { i = int.Parse(Console.ReadLine()); double y = x / i; Console.WriteLine($"{x}/{i}={y}"); } catch (System.DivideByZeroException e) { Console.WriteLine($"Деление на ноль! Исключение {e}"); } catch (System.FormatException e) { Console.WriteLine($"Введено не целое число! Исключение {e}"); } catch (System.OverflowException e) { Console.WriteLine($"Введите число в диапазоне от {int.MinValue} до {int.MaxValue}, исключая ноль. Исключение {e}"); }
здесь мы добавили сразу три блока catch
в каждом из которых происходит обработка исключений определенного типа. Для того, чтобы обработать исключение определенного типа мы использовали рядом с catch круглые скобки, в которых указали тип обрабатываемого исключения и соотнесли этот тип с именем исключения, которое в нашем случае было e
.
Следует также отметить, что далеко не всегда удается на этапе разработки предугадать абсолютна все типы исключений. Что, например, произойдет, если мы уберем из нашего кода блок, обрабатывающий System.OverflowException
? Правильно, мы снова нарвемся на аварийное завершение работы программы, так как компилятор пройдет по всем блокам catch
и не сможет соотнести тип исключение с именем. Чтобы такого не произошло, можно также предусмотреть при обработке исключений общий блок catch
в котором будет обрабатываться всё, что не попало в другие блоки. Например, мы можем сделать обработку двух типов исключений, а третий — обработаем в общем блоке:
catch (System.OverflowException e) { Console.WriteLine($"Введите число в диапазоне от {int.MinValue} до {int.MaxValue}, исключая ноль. Исключение {e}"); } catch (System.DivideByZeroException e) { Console.WriteLine($"Деление на ноль! Исключение {e}"); } //общий блок catch catch { Console.WriteLine("Неизвестная ошибка. Перезапустите программу"); }
Необходимо отметить, что важен не только факт наличия, но и порядок написания блоков catch
. Универсальный блок catch
должен находиться в самом низу кода. Об этом, кстати, Visual Studio сообщает. Если вы перенесете общий блок catch
и поставите его, например, над блоком, обрабатывающим исключение DivideByZeroException
, то Visual Studio выдаст ошибку:
Ошибка CS1017 Конструкции catch не могут использоваться после универсальной конструкции catch оператора try
Логические операции и обработка исключений в C#
Несмотря на то, что использование конструкции try..catch..finally
прекрасно позволяет перехватывать и обрабатывать различного типа исключения, её использование не всегда может быть оправдано, а некоторые исключения могут быть предвидены разработчиком и обработаны с использованием обычных логических операций. Например, в случае, если пользователь вводит не число, а непонятно что, можно было бы обойтись вот такой конструкцией:
if (int.TryParse(Console.ReadLine(), out i)) { y = x / i; Console.WriteLine($"{x}/{i}={y}"); } else { Console.WriteLine("Вы ввели не число!"); }
Здесь метод int.TryParse()
пробует преобразовать строку в целое число и, если преобразование прошло успешно, то возвращает true
. Таким образом, мы избежали использования конструкции try...catch
, которая, кстати, с точки зрения производительности более накладна, чем обычный условный оператор if
.
Итого
Сегодня мы познакомились с тем, как перехватывать и обрабатывать исключения в C#. Научились обрабатывать определенные типы исключений и в правильном порядке расставлять блоки catch в коде. Иногда мы можем повысить производительность нашего приложения, заменив, где это возможно и оправданно, конструкции try...catch
на обычные логические операции, например, используя условный оператор if
.
уважаемые посетители блога, если Вам понравилась, то, пожалуйста, помогите автору с лечением. Подробности тут.
Обработка исключений
Конструкция try..catch..finally
Последнее обновление: 30.12.2021
Иногда при выполнении программы возникают ошибки, которые трудно предусмотреть или предвидеть, а иногда и вовсе невозможно. Например, при передачи файла по сети может неожиданно оборваться сетевое подключение.
такие ситуации называются исключениями. Язык C# предоставляет разработчикам возможности для обработки таких ситуаций. Для этого
в C# предназначена конструкция try…catch…finally.
try { } catch { } finally { }
При использовании блока try…catch..finally вначале выполняются все инструкции в блоке try. Если в
этом блоке не возникло исключений, то после его выполнения начинает выполняться блок finally. И затем конструкция try..catch..finally
завершает свою работу.
Если же в блоке try вдруг возникает исключение, то обычный порядок выполнения останавливается, и среда CLR
начинает искать блок catch, который может обработать данное исключение. Если нужный блок
catch найден, то он выполняется, и после его завершения выполняется блок finally.
Если нужный блок catch не найден, то при возникновении исключения программа аварийно завершает свое выполнение.
Рассмотрим следующий пример:
int x = 5; int y = x / 0; Console.WriteLine($"Результат: {y}"); Console.WriteLine("Конец программы");
В данном случае происходит деление числа на 0, что приведет к генерации исключения. И при запуске приложения в
режиме отладки мы увидим в Visual Studio окошко, которое информирует об исключении:
В этом окошке мы видим, что возникло исключение, которое представляет тип System.DivideByZeroException,
то есть попытка деления на ноль. С помощью пункта View Details можно посмотреть более детальную информацию об исключении.
И в этом случае единственное, что нам остается, это завершить выполнение программы.
Чтобы избежать подобного аварийного завершения программы, следует использовать для обработки исключений конструкцию
try…catch…finally. Так, перепишем пример следующим образом:
try { int x = 5; int y = x / 0; Console.WriteLine($"Результат: {y}"); } catch { Console.WriteLine("Возникло исключение!"); } finally { Console.WriteLine("Блок finally"); } Console.WriteLine("Конец программы");
В данном случае у нас опять же возникнет исключение в блоке try, так как мы пытаемся разделить на ноль.
И дойдя до строки
int y = x / 0;
выполнение программы остановится. CLR найдет блок catch и передаст управление этому блоку.
После блока catch будет выполняться блок finally.
Возникло исключение! Блок finally Конец программы
Таким образом, программа по-прежнему не будет выполнять деление на ноль и соответственно не будет выводить результат этого деления,
но теперь она не будет аварийно завершаться, а исключение будет обрабатываться в блоке catch.
Следует отметить, что в этой конструкции обязателен блок try. При наличии блока catch мы можем опустить блок finally:
try { int x = 5; int y = x / 0; Console.WriteLine($"Результат: {y}"); } catch { Console.WriteLine("Возникло исключение!"); }
И, наоборот, при наличии блока finally мы можем опустить блок catch и не обрабатывать исключение:
try { int x = 5; int y = x / 0; Console.WriteLine($"Результат: {y}"); } finally { Console.WriteLine("Блок finally"); }
Однако, хотя с точки зрения синтаксиса C# такая конструкция вполне корректна, тем не менее, поскольку CLR не сможет найти нужный блок
catch, то исключение не будет обработано, и программа аварийно завершится.
Обработка исключений и условные конструкции
Ряд исключительных ситуаций может быть предвиден разработчиком. Например, пусть в программе есть метод, который принимает строку, конвертирует ее в число
и вычисляет квадрат этого числа:
Square("12"); // Квадрат числа 12: 144 Square("ab"); // !Исключение void Square(string data) { int x = int.Parse(data); Console.WriteLine($"Квадрат числа {x}: {x * x}"); }
Если пользователь передаст в метод не число, а строку, которая содежит нецифровые символы, то программа выпадет в ошибку. С одной стороны,
здесь как раз та ситуация, когда можно применить блок
try..catch
, чтобы обработать возможную ошибку. Однако гораздо оптимальнее было бы проверить допустимость преобразования:
Square("12"); // Квадрат числа 12: 144 Square("ab"); // Некорректный ввод void Square(string data) { if (int.TryParse(data, out var x)) { Console.WriteLine($"Квадрат числа {x}: {x * x}"); } else { Console.WriteLine("Некорректный ввод"); } }
Метод int.TryParse()
возвращает true
, если преобразование можно осуществить, и false
— если нельзя. При допустимости преобразования переменная x
будет содержать введенное число. Так, не используя try...catch
можно обработать возможную исключительную ситуацию.
С точки зрения производительности использование блоков try..catch
более накладно, чем применение условных конструкций. Поэтому по возможности
вместо try..catch лучше использовать условные конструкции на проверку исключительных ситуаций.