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

Смотрите моё видео на тему Исключения C#

Возникающие ошибки можно условно разделить на несколько основных типов.

  • Ошибки при работе с ресурсами компьютера. По сути, эти ошибки возникают при проблемах с жестким диском. Это ошибки чтения / записи данных, либо же выделения памяти для работы программы.
  • Если разработчик допустил оплошность при разработке логики программы, могут возникнуть ошибки логики приложения. Среди наиболее часто возникающих: деление на ноль и выход за пределы коллекции.
  • Пользовательские ошибки. Фактически, это ошибки ввода данных или некорректные действия пользователя, которые нельзя предусмотреть. Однако эти ошибки необходимо отлавливать и предотвращать их повторное возникновение.

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

Вот мы и подошли ближе к коду. Для обработки исключений используются четыре ключевых слова: try, catch. throw, finally. Также существуют классы исключительных ситуаций. Для всех исключений базовым классом является Exception. Класс содержит много полезного, но мы рассмотрим самые основы.

  • Data — подробная информация по исключению.
  • InnerException — данные о внутренних исключениях, ставших причиной исключительной ситуации.
  • Message — текстовое описание ошибки.
  • Source — имя сборки, которая сгенерировала исключение.
  • StackTrace — последовательность вызовов методов, в результате которой возникла ошибка.
Исключение при работе приложения

Блоки try catch

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

// Делимое.
var numerator = 0.0;

// Делитель.
var denominator = 0.0;

try
{
    Console.WriteLine("Введите числитель.");
    numerator = Convert.ToDouble(Console.ReadLine());
    Console.WriteLine("Введите знаменатель.", denominator);
    denominator = Convert.ToDouble(Console.ReadLine());
    Console.WriteLine($"Частное равно {numerator / denominator}");
}
catch(FormatException e)
{
    Console.WriteLine($"{e.Message} Введите число.");
}                       
Console.ReadLine();

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

Ошибки в приложениях с визуальным интерфейсом

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

Как вы видите, пользователь может продолжить исполнение программы после ошибки. По нажатию кнопки «Сведения» на экран будет выведена более подробная информация об ошибке. Несмотря на встроенный обработчик исключений следует обрабатывать ошибки самостоятельно. Как вариант, можно создать блок try, в котором отловить возникающую ошибку. А в блоке catch вывести сообщение об ошибке и соответствующим образом её обработать.

Генерирование исключительных ситуаций

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

private static void TestMethod(object obj)
{
    if (obj == null)
    {
        throw new ArgumentNullException($"{nameof(obj)}");
    }
}

Таким образом, мы прервем выполнение программы, если возникнет ошибка.

Блок finally

Ранее были мы рассмотрели только три ключевых слова: try, catch и throw. Исправим эту несправедливость и обратимся к finally. Удобство в том, что код в нем выполнится в любом случае, независимо от наличия или отсутствия ошибки в коде. К примеру, мы работаем с файлами. В таком случае, нам необходимо выполнить три действия: открыть файл, обработать данные, закрыть файл. Именно операция закрытия файла, которая должна выполняться в любом случае, выносится в блок finally.

Собственный класс исключения

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

    /// <summary>
    /// Пользовательский класс исключения.
    /// </summary>
    public class MyException : ApplicationException
    {
        /// <summary>
        /// Пользовательский объект.
        /// </summary>
        object _myObject;

        /// <summary>
        /// Сообщение об ошибке. 
        /// </summary>
        string _message; 

        /// <summary>
        /// Конструктор объекта исключения.
        /// </summary>
        /// <param name="obj"> Пользовательский объект.</param>
        /// <param name="message"> Текст сообщения.</param>
        public MyException(object obj, string message)
        {
            _myObject = obj;
            _message = message;
        }

        /// <summary>
        /// Пользовательский объект. 
        /// </summary>
        public object MyObject
        {
            get
            {
                return _myObject;
            }
        }

        /// <summary>
        /// Перегрузка свойства "Message", 
        /// чтобы вернуть сообщение пользовательского исключения.
        /// </summary>
        public override string Message
        {
            get
            {
                return _message;
            }
        }
    }

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

Полагаю, это необходимый минимум, достаточный, чтобы познакомиться с исключениями в C#. Удачных экспериментов, коллеги.

Кроме того, рекомендую прочитать статью Map C# | Структура данных словарь C# А также подписывайтесь на группу ВКонтакте, Telegram и YouTube-канал. Там еще больше полезного и интересного для программистов.