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

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

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

Пример исключения в 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.

уважаемые посетители блога, если Вам понравилась, то, пожалуйста, помогите автору с лечением. Подробности тут.
Подписаться
Уведомить о
guest
0 Комментарий
Межтекстовые Отзывы
Посмотреть все комментарии