C вывод ошибки try catch

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()); ...

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's user avatar

Carsten

11.1k7 gold badges38 silver badges60 bronze badges

asked Apr 22, 2013 at 10:55

apomene's user avatar

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

Darren's user avatar

DarrenDarren

67.8k24 gold badges135 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

Community's user avatar

answered Apr 22, 2013 at 10:57

Freelancer's user avatar

FreelancerFreelancer

8,9737 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 12345678's user avatar

User 12345678User 12345678

7,6542 gold badges28 silver badges45 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

Arshad's user avatar

ArshadArshad

9,5846 gold badges36 silver badges60 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

nassimlouchani's user avatar

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's user avatar

Alenros

8087 silver badges23 bronze badges

answered Jan 21, 2021 at 6:18

mekanaydemir's user avatar

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

Конструкция 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 окошко, которое информирует об исключении:

Исключения в C#

В этом окошке мы видим, что возникло исключение, которое представляет тип 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 лучше использовать условные конструкции на проверку исключительных ситуаций.

description title ms.date f1_keywords helpviewer_keywords ms.assetid

try-catch — C# Reference

try-catch — C# Reference

07/20/2015

try

try_CSharpKeyword

catch

catch_CSharpKeyword

catch keyword [C#]

try-catch statement [C#]

cb5503c7-bfa1-4610-8fc2-ddcd2e84c438

try-catch (C# Reference)

The try-catch statement consists of a try block followed by one or more catch clauses, which specify handlers for different exceptions.

When an exception is thrown, the common language runtime (CLR) looks for the catch statement that handles this exception. If the currently executing method does not contain such a catch block, the CLR looks at the method that called the current method, and so on up the call stack. If no catch block is found, then the CLR displays an unhandled exception message to the user and stops execution of the program.

The try block contains the guarded code that may cause the exception. The block is executed until an exception is thrown or it is completed successfully. For example, the following attempt to cast a null object raises the xref:System.NullReferenceException exception:

object o2 = null;
try
{
    int i2 = (int)o2;   // Error
}

Although the catch clause can be used without arguments to catch any type of exception, this usage is not recommended. In general, you should only catch those exceptions that you know how to recover from. Therefore, you should always specify an object argument derived from xref:System.Exception?displayProperty=nameWithType. The exception type should be as specific as possible in order to avoid incorrectly accepting exceptions that your exception handler is actually not able to resolve. As such, prefer concrete exceptions over the base Exception type. For example:

catch (InvalidCastException e)
{
    // recover from exception
}

It is possible to use more than one specific catch clause in the same try-catch statement. In this case, the order of the catch clauses is important because the catch clauses are examined in order. Catch the more specific exceptions before the less specific ones. The compiler produces an error if you order your catch blocks so that a later block can never be reached.

Using catch arguments is one way to filter for the exceptions you want to handle. You can also use an exception filter that further examines the exception to decide whether to handle it. If the exception filter returns false, then the search for a handler continues.

catch (ArgumentException e) when (e.ParamName == "")
{
    // recover from exception
}

Exception filters are preferable to catching and rethrowing (explained below) because filters leave the stack unharmed. If a later handler dumps the stack, you can see where the exception originally came from, rather than just the last place it was rethrown. A common use of exception filter expressions is logging. You can create a filter that always returns false that also outputs to a log, you can log exceptions as they go by without having to handle them and rethrow.

A throw statement can be used in a catch block to re-throw the exception that is caught by the catch statement. The following example extracts source information from an xref:System.IO.IOException exception, and then throws the exception to the parent method.

catch (FileNotFoundException e)
{
    // FileNotFoundExceptions are handled here.
}
catch (IOException e)
{
    // Extract some information from this exception, and then
    // throw it to the parent method.
    if (e.Source != null)
        Console.WriteLine("IOException source: {0}", e.Source);
    throw;
}

You can catch one exception and throw a different exception. When you do this, specify the exception that you caught as the inner exception, as shown in the following example.

catch (InvalidCastException e)
{
    // Perform some action here, and then throw a new exception.
    throw new YourCustomException("Put your error message here.", e);
}

You can also re-throw an exception when a specified condition is true, as shown in the following example.

catch (InvalidCastException e)
{
    if (e.Data == null)
    {
        throw;
    }
    else
    {
        // Take some action.
    }
}

[!NOTE]
It is also possible to use an exception filter to get a similar result in an often cleaner fashion (as well as not modifying the stack, as explained earlier in this document). The following example has a similar behavior for callers as the previous example. The function throws the InvalidCastException back to the caller when e.Data is null.

catch (InvalidCastException e) when (e.Data != null)
{
    // Take some action.
}

From inside a try block, initialize only variables that are declared therein. Otherwise, an exception can occur before the execution of the block is completed. For example, in the following code example, the variable n is initialized inside the try block. An attempt to use this variable outside the try block in the Write(n) statement will generate a compiler error.

static void Main()
{
    int n;
    try
    {
        // Do not initialize this variable here.
        n = 123;
    }
    catch
    {
    }
    // Error: Use of unassigned local variable 'n'.
    Console.Write(n);
}

For more information about catch, see try-catch-finally.

Exceptions in async methods

An async method is marked by an async modifier and usually contains one or more await expressions or statements. An await expression applies the await operator to a xref:System.Threading.Tasks.Task or xref:System.Threading.Tasks.Task%601.

When control reaches an await in the async method, progress in the method is suspended until the awaited task completes. When the task is complete, execution can resume in the method. For more information, see Asynchronous programming with async and await.

The completed task to which await is applied might be in a faulted state because of an unhandled exception in the method that returns the task. Awaiting the task throws an exception. A task can also end up in a canceled state if the asynchronous process that returns it is canceled. Awaiting a canceled task throws an OperationCanceledException.

To catch the exception, await the task in a try block, and catch the exception in the associated catch block. For an example, see the Async method example section.

A task can be in a faulted state because multiple exceptions occurred in the awaited async method. For example, the task might be the result of a call to xref:System.Threading.Tasks.Task.WhenAll%2A?displayProperty=nameWithType. When you await such a task, only one of the exceptions is caught, and you can’t predict which exception will be caught. For an example, see the Task.WhenAll example section.

Example

In the following example, the try block contains a call to the ProcessString method that may cause an exception. The catch clause contains the exception handler that just displays a message on the screen. When the throw statement is called from inside ProcessString, the system looks for the catch statement and displays the message Exception caught.

:::code language=»csharp» source=»./snippets/RefKeywordsExceptions.cs» id=»Snippet2″:::

Two catch blocks example

In the following example, two catch blocks are used, and the most specific exception, which comes first, is caught.

To catch the least specific exception, you can replace the throw statement in ProcessString with the following statement: throw new Exception().

If you place the least-specific catch block first in the example, the following error message appears: A previous catch clause already catches all exceptions of this or a super type ('System.Exception').

:::code language=»csharp» source=»./snippets/RefKeywordsExceptions.cs» id=»Snippet3″:::

Async method example

The following example illustrates exception handling for async methods. To catch an exception that an async task throws, place the await expression in a try block, and catch the exception in a catch block.

Uncomment the throw new Exception line in the example to demonstrate exception handling. The task’s IsFaulted property is set to True, the task’s Exception.InnerException property is set to the exception, and the exception is caught in the catch block.

Uncomment the throw new OperationCanceledException line to demonstrate what happens when you cancel an asynchronous process. The task’s IsCanceled property is set to true, and the exception is caught in the catch block. Under some conditions that don’t apply to this example, the task’s IsFaulted property is set to true and IsCanceled is set to false.

:::code language=»csharp» source=»./snippets/AsyncExceptionExamples.cs» id=»Snippet2″:::

Task.WhenAll example

The following example illustrates exception handling where multiple tasks can result in multiple exceptions. The try block awaits the task that’s returned by a call to xref:System.Threading.Tasks.Task.WhenAll%2A?displayProperty=nameWithType. The task is complete when the three tasks to which WhenAll is applied are complete.

Each of the three tasks causes an exception. The catch block iterates through the exceptions, which are found in the Exception.InnerExceptions property of the task that was returned by xref:System.Threading.Tasks.Task.WhenAll%2A?displayProperty=nameWithType.

:::code language=»csharp» source=»./snippets/AsyncExceptionExamples.cs» id=»Snippet4″:::

C# language specification

For more information, see The try statement section of the C# language specification.

See also

  • C# Reference
  • C# Programming Guide
  • C# Keywords
  • try, throw, and catch Statements (C++)
  • throw
  • try-finally
  • How to: Explicitly Throw Exceptions
  • xref:System.AppDomain.FirstChanceException
  • xref:System.AppDomain.UnhandledException

Время прочтения
4 мин

Просмотры 12K

Всё началось с безобидного пролистывания 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 записывает в журнал событий 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 также могут принимать категорию в виде строки ,которая может быть использована при обработке вывода.

An exception is a blocking error.

First of all, the best practice should be don’t throw exceptions for any kind of error, unless it’s a blocking error.

If the error is blocking, then throw the exception. Once the exception is already thrown, there’s no need to hide it because it’s exceptional; let the user know about it (you should reformat the whole exception to something useful to the user in the UI).

Your job as software developer is to endeavour to prevent an exceptional case where some parameter or runtime situation may end in an exception. That is, exceptions mustn’t be muted, but these must be avoided.

For example, if you know that some integer input could come with an invalid format, use int.TryParse instead of int.Parse. There is a lot of cases where you can do this instead of just saying «if it fails, simply throw an exception».

Throwing exceptions is expensive.

If, after all, an exception is thrown, instead of writing the exception to the log once it has been thrown, one of best practices is catching it in a first-chance exception handler. For example:

  • ASP.NET: Global.asax Application_Error
  • Others: AppDomain.FirstChanceException event.

My stance is that local try/catches are better suited for handling special cases where you may translate an exception into another, or when you want to «mute» it for a very, very, very, very, very special case (a library bug throwing an unrelated exception that you need to mute in order to workaround the whole bug).

For the rest of the cases:

  • Try to avoid exceptions.
  • If this isn’t possible: first-chance exception handlers.
  • Or use a PostSharp aspect (AOP).

Answering to @thewhiteambit on some comment…

@thewhiteambit said:

Exceptions are not Fatal-Errors, they are Exceptions! Sometimes they
are not even Errors, but to consider them Fatal-Errors is completely
false understanding of what Exceptions are.

First of all, how an exception can’t be even an error?

  • No database connection => exception.
  • Invalid string format to parse to some type => exception
  • Trying to parse JSON and while input isn’t actually JSON => exception
  • Argument null while object was expected => exception
  • Some library has a bug => throws an unexpected exception
  • There’s a socket connection and it gets disconnected. Then you try to send a message => exception

We might list 1k cases of when an exception is thrown, and after all, any of the possible cases will be an error.

An exception is an error, because at the end of the day it is an object which collects diagnostic information — it has a message and it happens when something goes wrong.

No one would throw an exception when there’s no exceptional case. Exceptions should be blocking errors because once they’re thrown, if you don’t try to fall into the use try/catch and exceptions to implement control flow they mean your application/service will stop the operation that entered into an exceptional case.

Also, I suggest everyone to check the fail-fast paradigm published by Martin Fowler (and written by Jim Shore). This is how I always understood how to handle exceptions, even before I got to this document some time ago.

[…] consider them Fatal-Errors is completely false understanding of what exceptions are.

Usually exceptions cut some operation flow and they’re handled to convert them to human-understandable errors. Thus, it seems like an exception actually is a better paradigm to handle error cases and work on them to avoid an application/service complete crash and notify the user/consumer that something went wrong.

More answers about @thewhiteambit concerns

For example in case of a missing Database-Connection the program could
exceptionally continue with writing to a local file and send the
changes to the Database once it is available again. Your invalid
String-To-Number casting could be tried to parse again with
language-local interpretation on Exception, like as you try default
English language to Parse(«1,5») fails and you try it with German
interpretation again which is completely fine because we use comma
instead of point as separator. You see these Exceptions must not even
be blocking, they only need some Exception-handling.

  1. If your app might work offline without persisting data to database, you shouldn’t use exceptions, as implementing control flow using try/catch is considered as an anti-pattern. Offline work is a possible use case, so you implement control flow to check if database is accessible or not, you don’t wait until it’s unreachable.

  2. The parsing thing is also an expected case (not EXCEPTIONAL CASE). If you expect this, you don’t use exceptions to do control flow!. You get some metadata from the user to know what his/her culture is and you use formatters for this! .NET supports this and other environments too, and an exception because number formatting must be avoided if you expect a culture-specific usage of your application/service.

An unhandled Exception usually becomes an Error, but Exceptions itself
are not codeproject.com/Articles/15921/Not-All-Exceptions-Are-Errors

This article is just an opinion or a point of view of the author.

Since Wikipedia can be also just the opinion of articule author(s), I wouldn’t say it’s the dogma, but check what Coding by exception article says somewhere in some paragraph:

[…] Using these exceptions to handle specific errors that arise to
continue the program is called coding by exception. This anti-pattern can quickly degrade software in performance and maintainability.

It also says somewhere:

Incorrect exception usage

Often coding by exception can lead to further issues in the software
with incorrect exception usage. In addition to using exception
handling for a unique problem, incorrect exception usage takes this
further by executing code even after the exception is raised. This
poor programming method resembles the goto method in many software
languages but only occurs after a problem in the software is detected.

Honestly, I believe that software can’t be developed don’t taking use cases seriously. If you know that…

  • Your database can go offline…
  • Some file can be locked…
  • Some formatting might be not supported…
  • Some domain validation might fail…
  • Your app should work in offline mode…
  • whatever use case

you won’t use exceptions for that. You would support these use cases using regular control flow.

And if some unexpected use case isn’t covered, your code will fail fast, because it’ll throw an exception. Right, because an exception is an exceptional case.

In the other hand, and finally, sometimes you cover exceptional cases throwing expected exceptions, but you don’t throw them to implement control flow. You do it because you want to notify upper layers that you don’t support some use case or your code fails to work with some given arguments or environment data/properties.

An exception is a blocking error.

First of all, the best practice should be don’t throw exceptions for any kind of error, unless it’s a blocking error.

If the error is blocking, then throw the exception. Once the exception is already thrown, there’s no need to hide it because it’s exceptional; let the user know about it (you should reformat the whole exception to something useful to the user in the UI).

Your job as software developer is to endeavour to prevent an exceptional case where some parameter or runtime situation may end in an exception. That is, exceptions mustn’t be muted, but these must be avoided.

For example, if you know that some integer input could come with an invalid format, use int.TryParse instead of int.Parse. There is a lot of cases where you can do this instead of just saying «if it fails, simply throw an exception».

Throwing exceptions is expensive.

If, after all, an exception is thrown, instead of writing the exception to the log once it has been thrown, one of best practices is catching it in a first-chance exception handler. For example:

  • ASP.NET: Global.asax Application_Error
  • Others: AppDomain.FirstChanceException event.

My stance is that local try/catches are better suited for handling special cases where you may translate an exception into another, or when you want to «mute» it for a very, very, very, very, very special case (a library bug throwing an unrelated exception that you need to mute in order to workaround the whole bug).

For the rest of the cases:

  • Try to avoid exceptions.
  • If this isn’t possible: first-chance exception handlers.
  • Or use a PostSharp aspect (AOP).

Answering to @thewhiteambit on some comment…

@thewhiteambit said:

Exceptions are not Fatal-Errors, they are Exceptions! Sometimes they
are not even Errors, but to consider them Fatal-Errors is completely
false understanding of what Exceptions are.

First of all, how an exception can’t be even an error?

  • No database connection => exception.
  • Invalid string format to parse to some type => exception
  • Trying to parse JSON and while input isn’t actually JSON => exception
  • Argument null while object was expected => exception
  • Some library has a bug => throws an unexpected exception
  • There’s a socket connection and it gets disconnected. Then you try to send a message => exception

We might list 1k cases of when an exception is thrown, and after all, any of the possible cases will be an error.

An exception is an error, because at the end of the day it is an object which collects diagnostic information — it has a message and it happens when something goes wrong.

No one would throw an exception when there’s no exceptional case. Exceptions should be blocking errors because once they’re thrown, if you don’t try to fall into the use try/catch and exceptions to implement control flow they mean your application/service will stop the operation that entered into an exceptional case.

Also, I suggest everyone to check the fail-fast paradigm published by Martin Fowler (and written by Jim Shore). This is how I always understood how to handle exceptions, even before I got to this document some time ago.

[…] consider them Fatal-Errors is completely false understanding of what exceptions are.

Usually exceptions cut some operation flow and they’re handled to convert them to human-understandable errors. Thus, it seems like an exception actually is a better paradigm to handle error cases and work on them to avoid an application/service complete crash and notify the user/consumer that something went wrong.

More answers about @thewhiteambit concerns

For example in case of a missing Database-Connection the program could
exceptionally continue with writing to a local file and send the
changes to the Database once it is available again. Your invalid
String-To-Number casting could be tried to parse again with
language-local interpretation on Exception, like as you try default
English language to Parse(«1,5») fails and you try it with German
interpretation again which is completely fine because we use comma
instead of point as separator. You see these Exceptions must not even
be blocking, they only need some Exception-handling.

  1. If your app might work offline without persisting data to database, you shouldn’t use exceptions, as implementing control flow using try/catch is considered as an anti-pattern. Offline work is a possible use case, so you implement control flow to check if database is accessible or not, you don’t wait until it’s unreachable.

  2. The parsing thing is also an expected case (not EXCEPTIONAL CASE). If you expect this, you don’t use exceptions to do control flow!. You get some metadata from the user to know what his/her culture is and you use formatters for this! .NET supports this and other environments too, and an exception because number formatting must be avoided if you expect a culture-specific usage of your application/service.

An unhandled Exception usually becomes an Error, but Exceptions itself
are not codeproject.com/Articles/15921/Not-All-Exceptions-Are-Errors

This article is just an opinion or a point of view of the author.

Since Wikipedia can be also just the opinion of articule author(s), I wouldn’t say it’s the dogma, but check what Coding by exception article says somewhere in some paragraph:

[…] Using these exceptions to handle specific errors that arise to
continue the program is called coding by exception. This anti-pattern can quickly degrade software in performance and maintainability.

It also says somewhere:

Incorrect exception usage

Often coding by exception can lead to further issues in the software
with incorrect exception usage. In addition to using exception
handling for a unique problem, incorrect exception usage takes this
further by executing code even after the exception is raised. This
poor programming method resembles the goto method in many software
languages but only occurs after a problem in the software is detected.

Honestly, I believe that software can’t be developed don’t taking use cases seriously. If you know that…

  • Your database can go offline…
  • Some file can be locked…
  • Some formatting might be not supported…
  • Some domain validation might fail…
  • Your app should work in offline mode…
  • whatever use case

you won’t use exceptions for that. You would support these use cases using regular control flow.

And if some unexpected use case isn’t covered, your code will fail fast, because it’ll throw an exception. Right, because an exception is an exceptional case.

In the other hand, and finally, sometimes you cover exceptional cases throwing expected exceptions, but you don’t throw them to implement control flow. You do it because you want to notify upper layers that you don’t support some use case or your code fails to work with some given arguments or environment data/properties.

Table of Contents

  • Overview
  • Syntax
  • Uncaught Exception
    • Code
    • Output
  • Handling Exception
    • Code
    • Output
  • Throwing Exception
    • Code
    • Output
  • Exception Bubbling
    • Code
    • Output
    • Explanation
    • Modified Code
    • Output
    • Explanation
  • Summary
  • Reference
  • See Also
  • Download

Overview

Exceptions are used to indicate that an error has occurred while running the program. Exception objects that describe an error are created and then thrown with the throw keyword. The runtime then searches for the most compatible exception handler.

In C# language’s exception handling features helps to deal with any unexpected or exceptional situations that occur when a program is running. Exception handling uses the
try, catch, and finally keywords to

  • Try actions that may not succeed
  • To handle failures when decided that it is reasonable to do so
  • To clean up resources afterwards

and Exceptions can be generated

  • By the common language runtime (CLR)
  • By the .NET Framework or any third-party libraries
  • Or by application code

Exceptions are created by using the
throw keyword.

 Return to
Top


Syntax

An error can occur at almost any statement. Checking for all these errors becomes unbearably complex. Exception handling separates this logic. It simplifies control flow. C# exception handling is built upon four keywords:
try, catch, finally, and
throw
.

The try block encloses the statements that might throw an exception whereas catch handles an exception if one exists. The finally can be used for doing any clean up process. The general form of try-catch-finally in C# is shown below.

try

{

   // Statement which can cause an exception.

}

catch(TypeOfException ex)

{

   // Statements for handling the exception

}

finally

{

   //Any cleanup code

}

 Return to
Top


Uncaught Exception

The following program will compile but will show an error during execution. The division by zero is a runtime anomaly and the program terminates with an error message. Any uncaught exceptions in the current context propagate to a higher context and looks
for an appropriate catch block to handle it. If it can’t find any suitable catch blocks then the default mechanism of the .NET runtime will terminate the execution of the entire program.

Code

using
System;

namespace
UncaughtException

{

    class
Program

    {

        static
void
Main(
string[] args)

        {

            int
x = 0;

            int
div = 100 / x;

            Console.WriteLine(div);

            Console.ReadLine();

        }

    }

}

Output

 Return to
Top


Handling Exception

In C# it provides a structured solution for the exception handling in the form of try and catch blocks. Using these blocks the core program statements are separated from the error-handling statements. As we see from the above example it can’t find any suitable
catch blocks then the default mechanism of the .NET runtime will terminate the execution of the entire program. To prevent this from happening we modified the above program with error handling blocks. These are implemented with
try, catch keywords. So, the modified form with the exception handling mechanism is as follows.

Code

using
System;

namespace
ExceptionHandling

{

    class
Program

    {

        static
void
Main(
string[] args)

        {

            try

            {

                int
x = 0;

                int
div = 100 / x;

                Console.WriteLine(div);

                Console.ReadLine();

            }

            catch(Exception ex)

            {

                Console.WriteLine("Error message: "
+ ex.ToString());

                Console.ReadLine();

            }

        }

    }

}

Output

 Return to
Top


Throwing Exception

It is possible to throw an exception programmatically. throw is basically like throwing an exception from that point, so the stack trace would only go to where we are issuing the «throw». This throwing exception is handled by catch block.
The throw statement is used for this purpose. The general form of throwing an exception is as follows.

Code

using
System;

namespace
ExceptionThrowing

{

    class
Program

    {

        static
void
Main(
string[] args)

        {

            try

            {

                throw
new
Exception();

            }

            catch(Exception ex)

            {

                Console.WriteLine("Error message: "
+ ex.ToString());

                Console.ReadLine();

            }

        }

    }

}

Output

 Return to
Top


Exception Bubbling

In the above examples, we saw that Exception is handled in the catch block. Where main() method runs the code and raised exception is handled in the catch block. Imagine the situation what happens if there are multiple nested function calls, and exception
occurred in the fourth or fifth nested call.

  • Function1(): Calls Function2 within the try block and handles the exception in catch block
  • Function2(): Makes a call to the function Function3. But it neither wraps the call for Function3 in the try block nor has the exception handler
  • Function3(): Makes a call to the function Function4. But it neither wraps the call for Function4 in the try block nor has the exception handler
  • Function4(): Makes a call to the function Function5. But it neither wraps the call for Function5 in the try block nor has the exception handler
  • Function5(): Raises an Exception

Note, when the exception is thrown by the function Function5, even though the caller is
Function2, as there is no catch handler, the execution comes out of
Function2 and enters the catch block of Function1. Travelling back from

          Function5 -> Function4 -> Function3 ->
Function2 -> Function1

is known as Stack Unwinding. And exception occurred in Function5 is handled in
Function1 even when there is no handler at Function2 is known as
Exception Bubbling.

So, when we define a try/catch block, the handler defined in the
catch block will catch exceptions that originate directly from the code in the try block. The handler will also catch exceptions that originate in methods called from the code in the try block, or from code that those methods call. In short,
a catch block will catch any exceptions thrown by code that executes as a result of executing the block of code in the try block. (Assuming that the exception is not caught elsewhere).

Below is the example that demonstrates the Exception Bubbling.

Code

using
System;

namespace
ExceptionBubbling

{

    class
Program

    {

        static
void
Main(
string[] args)

        {

            try

            {

                Console.WriteLine("Making Call to Function1()");

                Function1();

                Console.WriteLine("Successfully returned from Function1()");

            }

            catch
(Exception ex)

            {

                Console.WriteLine("nFollowing exception occured:nn"
+ ex);

            }

            finally

            {

                Console.WriteLine("nnInside finally block.");

                Console.ReadLine();

            }

        }

        public
static
void
Function1()

        {

            Console.WriteLine("Inside Function1 -> Making Call to Function2()");

            Function2();

            Console.WriteLine("Successfully returned from Function2()");

        }

        public
static
void
Function2()

        {

            Console.WriteLine("Inside Function2 -> Making Call to Function3()");

            Function3();

            Console.WriteLine("Successfully returned from Function3()");

        }

        public
static
void
Function3()

        {

            Console.WriteLine("Inside Function3 -> Making Call to Function4()");

            Function4();

            Console.WriteLine("Successfully returned from Function4()");

        }

        public
static
void
Function4()

        {

            Console.WriteLine("Inside Function4 -> Making Call to Function5()");

            Function5();

            Console.WriteLine("Successfully returned from Function5()");

        }

        public
static
void
Function5()

        {

            Console.WriteLine("Inside Function5");

            throw
new
Exception();           

        }

    }

}

Output

Explanation

As we can see from the above output exception occurred in Function5 is passed to its calling function
Function4 because there is no catch block to handle it and this continues till it reaches to
Main() method as catch block or exception handling mechanism is only implemented here. And it enters the
finally block of Main() method

While finding the catch block even though other function didn’t through the exception the stack is getting filled with them in backward. For this while stack should filled with one exception it is getting filled and bubbling up with five exceptions.

Now let’s say we had a try/ catch block handled in Function5 then what would happen? As
Function5 is wrap with try/ catch block the exception will be handled there and code will return successfully.

Below is the example that demonstrates the Exception Handling:

Modified Code

using
System;

namespace
ExceptionBubbling

{

    class
Program

    {

        static
void
Main(
string[] args)

        {

            try

            {

                Console.WriteLine("Making Call to Function1()");

                Function1();

                Console.WriteLine("Successfully returned from Function1()");

            }

            catch
(Exception ex)

            {

                Console.WriteLine("nFollowing exception occured:nn"
+ ex);

            }

            finally

            {

                Console.WriteLine("nnInside finally block.");

                Console.ReadLine();

            }

        }

        public
static
void
Function1()

        {

            Console.WriteLine("Inside Function1 -> Making Call to Function2()");

            Function2();

            Console.WriteLine("Successfully returned from Function2()");

        }

        public
static
void
Function2()

        {

            Console.WriteLine("Inside Function2 -> Making Call to Function3()");

            Function3();

            Console.WriteLine("Successfully returned from Function3()");

        }

        public
static
void
Function3()

        {

            Console.WriteLine("Inside Function3 -> Making Call to Function4()");

            Function4();

            Console.WriteLine("Successfully returned from Function4()");

        }

        public
static
void
Function4()

        {

            Console.WriteLine("Inside Function4 -> Making Call to Function5()");

            Function5();

            Console.WriteLine("Successfully returned from Function5()");

        }

        public
static
void
Function5()

        {

            /*Console.WriteLine("Inside Function5");

            throw new Exception();*/

            //Exception handled

            try

            {

                Console.WriteLine("Inside Function5");

                throw
new
Exception();

            }

            catch
(Exception ex)

            {

                Console.WriteLine("nFollowing exception occured:nn"
+ ex);

            }

            finally

            {

                Console.WriteLine("nnInside finally block of Function5().");

                Console.ReadLine();

            }

        }

    }

}

Output

If we comment out the finally block in Function5() code will goto to the finally block in the main() method in backward.

Explanation

Here we can see that there is an exception handling mechanism implemented in the
Function5 so code is not going backward to find catch block and filling the stack. As exception is handled there code is then going backward to
finally block in main() and returning successfully from each function.

 Return to
Top


Summary

By now, we should have good understanding of what is an exception bubbling. An Exception will be caught in the inner catch block if appropriate filter found, otherwise will be
caught by outer catch block. We can implement
try/ catch block to prevent exception to bubbling up. Also we know how to clean up resources by implementing a
finally block whose code is always executed before leaving a method.

 Return to
Top


Reference

  • Exception Handling Fundamentals
    here
  • Best Practices for Exceptions
    here
  • Exception Handling Statements
    here
  • Handling and Throwing Exceptions
    here
  • Managing Exceptions with the Debugger
    here

 Return to
Top


See Also

  • Exception
    Handling Best Practices in .NET
  • Best
    Practices — Exception Handling in C# .NET
  • How
    using try catch for exception handling is best practice

 Return to
Top


Download

Download the Source Code used in the example from this link
Download Source Code

 Return to
Top


web analytics  

Содержание

  • Пример исключения в 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 выше всё, что мы сделали — это вывели одно сообщение о том, что пользователь ввел неверное значение. При этом, при разработке реальных приложений часто необходимо не только сообщить пользователю об исключении, но и постараться направить его на «путь истинный». Например, в нашем тестовом приложении пользователь, как мы определили может:

  1. ввести 0 (исключение System.DivideByZeroException)
  2. ввести вместо целого числа строку (исключение System.FormatException)
  3. ввести вместо целого числа число с плавающей запятой (исключениеSystem.FormatException)
  4. ввести число, превышающее максимальное значение 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.

уважаемые посетители блога, если Вам понравилась, то, пожалуйста, помогите автору с лечением. Подробности тут.

Понравилась статья? Поделить с друзьями:
  • C windows minidump ошибка как исправить
  • C windows memory dmp ошибка
  • C windows memory dmp как исправить
  • C windows diagnostics system networking ошибка
  • C windows diagnostics system networking 0x800b010a как исправить