Как отловить несколько типов исключений в одном блоке catch в C#?
Краткий ответ: да, можно, но не через запятую
В 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), где нужно унифицировать обработку ошибок. Минус — больше кода при выбрасывании, но плюс — чистая логика обработки.