Avatar ·

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

Всем привет! Я начинающий C# разработчик, и у меня возникла проблема с обработкой исключений. У меня есть метод, который выполняет операции с файлами и базой данных. Я хочу обрабатывать разные типы исключений (например, `FileNotFoundException`, `IOException`, `SqlException` и `ArgumentException`) одинаково — логировать ошибку и показывать пользователю сообщение. Сейчас мой код выглядит так: ```csharp try { // какой-то код, который может выбросить разные исключения } catch (FileNotFoundException ex) { LogError(ex); ShowMessage("Файл не найден"); } catch (IOException ex) { LogError(ex); ShowMessage("Ошибка ввода-вывода"); } catch (SqlException ex) { LogError(ex); ShowMessage("Ошибка базы данных"); } catch (ArgumentException ex) { LogError(ex); ShowMessage("Неверный аргумент"); } ``` Это очень громоздко и повторяет один и тот же код. Я слышал, что в других языках (например, Java или Python) можно перехватывать несколько исключений в одном блоке catch. Есть ли способ сделать так же в C#? Может быть, есть какой-то синтаксис вроде `catch (FileNotFoundException | IOException ex)`? Я использую .NET Framework 4.7.2, но если есть решение для более новых версий, тоже интересно. Заранее спасибо! <<>>

Решение: перехват нескольких исключений в C#

В C# нет прямого синтаксиса catch (ExType1 | ExType2 ex), как в Java. Однако есть несколько элегантных способов решить вашу проблему. Самый простой и рекомендуемый — использовать базовый класс исключения или паттерн фильтрации исключений (доступен с C# 6.0 / .NET 4.6+).

Способ 1: Использование базового класса (если логика одинаковая)

Если все исключения, которые вы хотите обработать, наследуются от общего предка (например, SystemException или Exception), можно просто поймать его. Но будьте осторожны — это может скрыть неожиданные ошибки.

try
{
    // код
}
catch (Exception ex) when (ex is FileNotFoundException 
                            || ex is IOException 
                            || ex is SqlException 
                            || ex is ArgumentException)
{
    LogError(ex);
    ShowMessage("Произошла ошибка: " + ex.Message);
}

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

Способ 2: Использование паттерна switch (C# 7.0+)

Если вам нужно не только перехватить, но и выполнить разную логику для разных типов, используйте catch (Exception ex) и внутри — switch с типами:

try
{
    // код
}
catch (Exception ex)
{
    switch (ex)
    {
        case FileNotFoundException:
            LogError(ex);
            ShowMessage("Файл не найден");
            break;
        case IOException:
            LogError(ex);
            ShowMessage("Ошибка ввода-вывода");
            break;
        case SqlException:
            LogError(ex);
            ShowMessage("Ошибка базы данных");
            break;
        case ArgumentException:
            LogError(ex);
            ShowMessage("Неверный аргумент");
            break;
        default:
            throw; // пробрасываем неожиданные исключения
    }
}

Способ 3: Общий блок catch с повторным выбрасыванием

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

try
{
    // код
}
catch (Exception ex) when (ex is FileNotFoundException 
                            or IOException 
                            or SqlException 
                            or ArgumentException)
{
    // Обработка
}

Обратите внимание: when принимает логическое выражение. Вы можете использовать or (C# 9.0+) или старый синтаксис ||.

Важные рекомендации

  • Не ловите Exception без фильтра — это скроет критические ошибки (например, OutOfMemoryException, StackOverflowException).
  • Используйте фильтр when — он предотвращает перехват исключения, если условие ложно, что позволяет сохранить стек вызовов.
  • Если вы используете .NET Framework 4.7.2, у вас есть доступ к C# 7.x, поэтому все способы выше работают.

Выберите тот вариант, который лучше подходит под вашу задачу. Лично я рекомендую фильтр исключений — он лаконичен и эффективен.

<<>>

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

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

private void HandleException(Exception ex, string message)
{
    LogError(ex);
    ShowMessage(message);
}

try
{
    // код
}
catch (FileNotFoundException ex)
{
    HandleException(ex, "Файл не найден");
}
catch (IOException ex)
{
    HandleException(ex, "Ошибка ввода-вывода");
}
catch (SqlException ex)
{
    HandleException(ex, "Ошибка базы данных");
}
catch (ArgumentException ex)
{
    HandleException(ex, "Неверный аргумент");
}

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

Ещё один вариант — использовать общий обработчик через событие AppDomain.CurrentDomain.FirstChanceException (для глобального логирования), но это уже экзотика и не рекомендуется для простых сценариев.