Содержание
При разработке программного обеспечения мало составить и реализовать какой-либо алгоритм, важно также предусмотреть всевозможные непредвиденные ситуации при работе вашей программы и, в случае необходимости отловить и обработать исключения, которые могут возникнуть. Например, вы решили разработать программу-клиент для работы с блогом, которая позволяет публиковать статьи, модерировать комментарии и выполнять прочую полезную работу. Как бы вы не старались сделать свое приложение работоспособным, неизбежно, при работе с программой пользователь может столкнуться с такими проблемами: сайт недоступен (например, в результате ошибки сервера 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
. В результате, программа не завершит работу аварийно, а выведет в консоль сообщение. Вот вывод консоли:
Приложение так же, как и в предыдущем примере, дошло до строки
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 выдаст ошибку:
Логические операции и обработка исключений в 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
.