Как обрабатывать несколько типов исключений в try-catch в C#?

Ответ

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

Пример с несколькими блоками catch:

try
{
    // Код, который может вызвать разные исключения
    var data = File.ReadAllText("config.json");
    var settings = JsonSerializer.Deserialize<Settings>(data);
}
catch (FileNotFoundException ex) // Наиболее специфичное
{
    Console.WriteLine($"Файл конфигурации не найден: {ex.FileName}");
    // Создаем файл с настройками по умолчанию
}
catch (JsonException ex)
{
    Console.WriteLine($"Ошибка парсинга JSON: {ex.Message}");
    // Используем дефолтные настройки
}
catch (IOException ex) // Более общее исключение ввода-вывода
{
    Console.WriteLine($"Ошибка ввода-вывода: {ex.Message}");
}
catch (Exception ex) // Самый общий блок — должен быть последним
{
    Console.WriteLine($"Непредвиденная ошибка: {ex.Message}");
    throw; // Пробрасываем выше, если не можем обработать
}
finally
{
    // Этот блок выполнится всегда, даже если было исключение
    Console.WriteLine("Операция чтения конфигурации завершена.");
}

Дополнительные возможности:

  1. Фильтры исключений (when): Позволяют добавить условие для входа в блок catch.

    catch (Exception ex) when (ex.InnerException is SqlException)
    {
        // Обрабатываем только если внутреннее исключение — SqlException
        LogDatabaseError(ex.InnerException);
    }
  2. Перехват нескольких типов в одном блоке (C# 6.0+):

    catch (ArgumentException or InvalidOperationException ex)
    {
        // Обрабатываем ArgumentException ИЛИ InvalidOperationException
        Console.WriteLine($"Некорректный аргумент или операция: {ex.Message}");
    }

Практические рекомендации:

  • Избегайте пустых блоков catch. Это скрывает ошибки.
  • Используйте повторный выброс (throw;), а не throw ex;. Первый вариант сохраняет оригинальный стек вызовов.
  • Блок finally идеально подходит для освобождения ресурсов (закрытие файлов, соединений с БД).

Ответ 18+ 🔞

Смотри, про исключения в C# — это как с женой в магазине. Если она сначала начнёт орать про то, что ты вообще мудак и всё забываешь, а потом уже про конкретную соль, которую не купил, — ты нихуя не поймёшь. Так и тут: ловить надо от конкретного к общему, а то общий Exception всё схавает, как я водку в пятницу, и более специфичные обработчики просто не сработают.

Вот смотри, как это выглядит в коде, на реальном примере:

try
{
    // Код, который может вызвать разные исключения
    var data = File.ReadAllText("config.json");
    var settings = JsonSerializer.Deserialize<Settings>(data);
}
catch (FileNotFoundException ex) // Самый конкретный случай — файла нет
{
    Console.WriteLine($"Файл конфигурации не найден: {ex.FileName}");
    // Создаем файл с настройками по умолчанию
}
catch (JsonException ex) // Чуть более общее — файл есть, но внутри какая-то хуйня
{
    Console.WriteLine($"Ошибка парсинга JSON: {ex.Message}");
    // Используем дефолтные настройки
}
catch (IOException ex) // Ещё общее — любая ошибка ввода-вывода
{
    Console.WriteLine($"Ошибка ввода-вывода: {ex.Message}");
}
catch (Exception ex) // Абсолютно всё, что летит — ловим в самом конце!
{
    Console.WriteLine($"Непредвиденная ошибка: {ex.Message}");
    throw; // И да, кидаем это добро дальше, если сами не справились
}
finally
{
    // Этот блок выполнится ВСЕГДА, даже если всё пошло по пизде.
    // Как твоя тёща, которая заявится в гости, даже если у тебя похмелье овердохуища.
    Console.WriteLine("Операция чтения конфигурации завершена.");
}

А ещё есть фишки, которые жизнь облегчают:

  1. Фильтры (when): Это как сказать "я налью тебе только если ты не трогал моё пиво".

    catch (Exception ex) when (ex.InnerException is SqlException)
    {
        // Сработает только если внутри сидит исключение от базы данных
        LogDatabaseError(ex.InnerException);
    }
  2. Несколько типов в одном catch (C# 6.0+): Чтоб не плодить одинаковый код.

    catch (ArgumentException or InvalidOperationException ex)
    {
        // Ловим либо одно, либо другое — похуй, обрабатываем одинаково
        Console.WriteLine($"Некорректный аргумент или операция: {ex.Message}");
    }

Главное, что нужно запомнить:

  • Не оставляй пустые catch блоки. Это пиздец как плохо. Сказал бы тебе почему, но ты всё равно забудешь, а ошибка потом всплывёт в самом неожиданном месте, и будешь ебаться с дебагом полдня.
  • Если кидаешь исключение заново — используй голый throw;, а не throw ex;. Первый вариант не трогает стек вызовов, и ты увидишь, откуда ошибка пришла на самом деле. Второй — затрёт историю, и ты будешь гадать, какого хуя.
  • Блок finally — твой лучший друг для уборки. Закрывай там файлы, соединения и прочие ресурсы. Он выполнится в любом случае, даже если в try всё взорвалось.