Avatar ·

Как отловить несколько типов исключений в одном блоке catch в C#?

Всем привет! Я начинающий C# разработчик, и у меня возникла проблема с обработкой исключений. Пишу небольшое приложение для работы с файлами и базой данных. У меня есть код, который может выбросить несколько разных исключений: например, `FileNotFoundException` (если файл не найден), `IOException` (проблемы с чтением) и `SqlException` (ошибка подключения к БД). Сейчас я обрабатываю их так: ```csharp try { // код, который может выбросить несколько исключений } catch (FileNotFoundException ex) { Console.WriteLine("Файл не найден: " + ex.Message); } catch (IOException ex) { Console.WriteLine("Ошибка ввода/вывода: " + ex.Message); } catch (SqlException ex) { Console.WriteLine("Ошибка базы данных: " + ex.Message); } ``` Но это выглядит очень громоздко, особенно если для всех исключений нужно выполнить одно и то же действие (например, записать в лог и показать пользователю сообщение об ошибке). Я слышал, что в некоторых языках можно перечислять исключения через запятую в одном catch. Можно ли так сделать в C#? Или есть какой-то более элегантный способ, не плодя кучу блоков catch? Заранее спасибо за помощь! <<>>

Краткий ответ: да, можно, но не через запятую

В C# нельзя перечислять типы исключений через запятую в одном блоке catch (как в Java или Python). Однако есть несколько элегантных способов объединить обработку нескольких исключений.

Способ 1: Классический — несколько catch с одинаковым кодом (не рекомендуется)

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

try
{
    // опасный код
}
catch (FileNotFoundException ex)
{
    HandleException(ex);
}
catch (IOException ex)
{
    HandleException(ex);
}
catch (SqlException ex)
{
    HandleException(ex);
}

void HandleException(Exception ex)
{
    Console.WriteLine($"Ошибка: {ex.Message}");
    LogError(ex);
}

Это решает проблему дублирования, но всё ещё требует много блоков catch.

Способ 2: Базовый класс Exception с проверкой типа (C# 6.0+)

Можно отловить базовый Exception и внутри проверить тип с помощью when:

try
{
    // код
}
catch (Exception ex) when (ex is FileNotFoundException || 
                           ex is IOException || 
                           ex is SqlException)
{
    Console.WriteLine($"Ошибка: {ex.Message}");
    LogError(ex);
}

Этот подход лаконичен и работает начиная с C# 6.0. Фильтр when позволяет точно указать, какие исключения мы хотим поймать, а остальные (например, NullReferenceException) пролетят дальше.

Способ 3: Использование pattern matching (C# 7.0+)

Ещё более современный способ — использовать pattern matching в блоке catch:

try
{
    // код
}
catch (Exception ex) when (ex is FileNotFoundException or IOException or SqlException)
{
    Console.WriteLine($"Ошибка: {ex.Message}");
    LogError(ex);
}

Ключевое слово or делает код очень читаемым. Это мой любимый способ.

Способ 4: Если все исключения наследуются от одного родителя

Иногда можно создать своё исключение-обёртку или использовать общий предок. Например, IOException — родитель для FileNotFoundException, DirectoryNotFoundException и DriveNotFoundException. Если вам нужно отловить все эти три, достаточно написать:

catch (IOException ex)
{
    // поймает все его наследники
}

Что выбрать?

  • Для C# 6.0 и ниже — используйте несколько catch с вызовом общего метода.
  • Для C# 6.0+ — фильтр when с проверкой через ||.
  • Для C# 7.0+ — pattern matching с or (самый читаемый вариант).
  • Если исключения имеют общего предка — ловите предка.

Важное замечание

Не ловите базовый Exception без фильтра, если не уверены, что сможете корректно обработать все возможные ошибки (например, OutOfMemoryException или StackOverflowException). Фильтр when — ваш друг!

<<>>

Альтернативный подход: создание своего класса исключения

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

public class MyAppException : Exception
{
    public MyAppException(string message, Exception innerException) 
        : base(message, innerException) { }
}

try
{
    // код, который может выбросить разные исключения
}
catch (FileNotFoundException ex)
{
    throw new MyAppException("Файл не найден", ex);
}
catch (IOException ex)
{
    throw new MyAppException("Ошибка ввода-вывода", ex);
}
// ... и так далее

Затем на верхнем уровне просто ловите MyAppException. Это особенно удобно в многослойных приложениях (например, в сервисах или API), где нужно унифицировать обработку ошибок. Минус — больше кода при выбрасывании, но плюс — чистая логика обработки.