← Назад к вопросам

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

2.2 Middle🔥 192 комментариев
#Основы C# и .NET

Комментарии (2)

🐱
deepseek-v3.2PrepBro AI6 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Обработка нескольких типов исключений в C#

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

Базовый синтаксис с несколькими catch-блоками

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

try
{
    // Код, который может вызвать исключения
    int result = DivideNumbers(10, 0);
    File.WriteAllText("file.txt", result.ToString());
}
catch (DivideByZeroException ex)
{
    // Обработка строго деления на ноль
    Console.WriteLine($"Деление на ноль: {ex.Message}");
    // Логика восстановления: установка значения по умолчанию
}
catch (FileNotFoundException ex)
{
    // Обработка отсутствия файла
    Console.WriteLine($"Файл не найден: {ex.FileName}");
    // Логика: создание файла или уведомление пользователя
}
catch (IOException ex) 
{
    // Более общий тип для всех ошибок ввода-вывода
    // Перехватит FileNotFoundException, если тот не пойман выше
    Console.WriteLine($"Ошибка IO: {ex.Message}");
}
catch (Exception ex)
{
    // Универсальный обработчик всех остальных исключений
    // Всегда должен быть последним!
    Console.WriteLine($"Неизвестная ошибка: {ex.Message}");
    // Логирование, аварийное завершение операции
}
finally
{
    // Код, выполняемый всегда (например, освобождение ресурсов)
}

Важные аспекты и best practices

  1. Специфичность прежде всего: Всегда начинайте с обработки конкретных исключений (DivideByZeroException, FileNotFoundException), затем переходите к более общим (IOException, Exception). Это позволяет применять точечную логику восстановления.

  2. Фильтры исключений (when): В C# 6.0 появились фильтры исключений, которые позволяют уточнять условие перехвата исключения без раскрутки стека.

try
{
    // Некоторый код
}
catch (HttpRequestException ex) when (ex.StatusCode == System.Net.HttpStatusCode.NotFound)
{
    Console.WriteLine("Ресурс не найден (404)");
}
catch (HttpRequestException ex) when (ex.StatusCode == System.Net.HttpStatusCode.Unauthorized)
{
    Console.WriteLine("Ошибка авторизации (401)");
}
catch (HttpRequestException ex)
{
    Console.WriteLine($"Другая HTTP-ошибка: {ex.StatusCode}");
}
  1. Минимизация кода в try-блоке: Помещайте в try только тот код, который действительно может вызвать исключения. Это улучшает читаемость и производительность.

  2. Избегание пустых catch-блоков: Пустой catch — антипаттерн, который скрывает ошибки. Даже если исключение "не важно", залогируйте его.

// ПЛОХО: исключение полностью игнорируется
catch (FormatException) { }

// ЛУЧШЕ: хотя бы логируем
catch (FormatException ex) 
{ 
    _logger.LogDebug(ex, "Некритичная ошибка формата");
}
  1. Повторная генерация исключений: Используйте throw; (сохраняет оригинальный stack trace), а не throw ex; (перезаписывает stack trace).
catch (Exception ex)
{
    _logger.LogError(ex, "Ошибка при обработке");
    throw; // Правильно: оригинальный stack trace сохраняется
}

Практический пример: многоуровневая обработка в веб-приложении

public async Task<IActionResult> UpdateUserData(int userId, UserData data)
{
    try
    {
        // Валидация
        if (data == null) throw new ArgumentNullException(nameof(data));
        
        // Бизнес-логика
        var user = await _repository.GetUserAsync(userId);
        if (user == null) throw new NotFoundException($"User {userId} not found");
        
        // Обновление данных
        await _repository.UpdateUserAsync(userId, data);
        
        return Ok();
    }
    catch (ArgumentException ex)
    {
        // Клиент передал некорректные аргументы
        return BadRequest(ex.Message);
    }
    catch (NotFoundException ex)
    {
        // Сущность не найдена - специфичная бизнес-ошибка
        return NotFound(ex.Message);
    }
    catch (DbUpdateConcurrencyException ex)
    {
        // Конфликт параллельного доступа к БД
        return Conflict("Данные были изменены другим пользователем");
    }
    catch (DbUpdateException ex)
    {
        // Общая ошибка обновления БД
        _logger.LogError(ex, "Database update failed");
        return StatusCode(500, "Ошибка при сохранении данных");
    }
    catch (Exception ex)
    {
        // Непредвиденные ошибки
        _logger.LogCritical(ex, "Unexpected error in UpdateUserData");
        return StatusCode(500, "Внутренняя ошибка сервера");
    }
}

Выводы

Грамотная обработка нескольких исключений требует:

  • Иерархического подхода от специфичных к общим исключениям
  • Семантической группировки ошибок по доменному контексту
  • Адекватной реакции на каждый тип ошибки (повторная попытка, fallback-значение, уведомление пользователя)
  • Обязательного логирования для диагностики проблем
  • Использования finally для гарантированного освобождения ресурсов

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

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