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

Как применяешь catch?

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

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

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

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

Применение блока catch в C#

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

Основные стратегии применения

  1. Конкретная обработка исключений
    Наиболее правильный подход — перехватывать конкретные типы исключений, которые вы ожидаете и можете осмысленно обработать.

```csharp
try
{
    var fileContent = File.ReadAllText("config.json");
    var config = JsonSerializer.Deserialize<Config>(fileContent);
}
catch (FileNotFoundException ex) // Конкретное исключение
{
    // Логика восстановления: создаем файл с конфигурацией по умолчанию
    Log.Warning($"Файл конфигурации не найден: {ex.FileName}. Создаю default.");
    CreateDefaultConfig();
}
catch (JsonException ex) // Другое конкретное исключение
{
    // Логика для ошибок парсинга JSON
    Log.Error($"Ошибка формата JSON: {ex.Message}");
    throw new InvalidOperationException("Неверный формат конфигурации", ex); // Обертывание
}
```

2. Логирование и аудит

    Один из самых частых сценариев — **логирование** деталей исключения перед дальнейшей передачей или обработкой. Никогда не используйте пустой `catch`.

```csharp
catch (SqlException ex)
{
    // Логируем все детали для последующего анализа
    _logger.LogError(ex, "Ошибка базы данных при запросе пользователя {UserId}. Код: {ErrorCode}", userId, ex.Number);
    // Возвращаем пользователю понятное сообщение, не раскрывая внутренних деталей
    throw new ApplicationException("Временные проблемы с сервисом. Попробуйте позже.");
}
```

3. Очистка ресурсов и повторные попытки (Retry Pattern)

    В сочетании с блоками `try-catch` внутри циклов или с использованием библиотек (например, **Polly**) реализуются стратегии повторных попыток для временных сбоев (сетевые ошибки, таймауты БД).

```csharp
int retries = 3;
for (int i = 0; i < retries; i++)
{
    try
    {
        await _httpClient.GetAsync("https://api.example.com/data");
        break; // Успех, выходим из цикла
    }
    catch (HttpRequestException ex) when (i < retries - 1) // Фильтр исключений (C# 6+)
    {
        _logger.LogWarning($"Попытка {i+1} не удалась. Повтор через 1 сек.");
        await Task.Delay(1000);
    }
}
```

4. Преобразование исключений (Exception Wrapping)

    Важная практика для **абстракции слоев** приложения. Исключение низкоуровневого слоя (например, `SqlException`) оборачивается в исключение уровня домена или приложения, скрывая детали реализации.

```csharp
catch (SqlException ex) when (ex.Number == 547) // Ошибка FOREIGN KEY constraint
{
    // Преобразуем специфичное для БД исключение в доменное
    throw new DomainConstraintViolationException("Невозможно удалить объект, так как он связан с другими данными.", ex);
}
```

Критические принципы и лучшие практики

  • Избегайте catch (Exception ex) на нижних уровнях: Перехват общего Exception оправдан только на самом верхнем уровне (например, в middleware ASP.NET Core), где нужно гарантировать возврат корректного HTTP-ответа (500) и логирование. В середине кода это скроет серьезные ошибки.

  • Фильтры исключений (when): Используйте их для более точного отлова исключений по условию, что часто заменяет множество if внутри блока catch.

  • Не подавляйте исключения без веской причины: Пустой блок catch — это антипаттерн, который делает отладку невозможной.

  • Порядок блоков catch: Располагайте обработчики от наиболее специфичных к наиболее общим. Компилятор не позволит поместить catch (Exception) перед catch (SpecificException).

  • Использование в асинхронном коде (async/await): Для асинхронных методов исключения перехватываются так же, но важно убедиться, что весь контекст (например, HttpContext в ASP.NET) доступен при обработке. Чаще всего catch используется внутри async-метода.

    public async Task<Data> GetDataAsync(int id)
    {
        try
        {
            return await _repository.LoadAsync(id);
        }
        catch (DataLayerException ex)
        {
            _logger.LogError(ex, "Ошибка слоя данных для ID={Id}", id);
            // Возвращаем null или throw в зависимости от контракта метода
            return null;
        }
    }
    
  • Взаимодействие с finally: Блок finally выполняется всегда, независимо от того, было исключение или нет. В catch вы обрабатываете ошибку, а в finally — гарантированно освобождаете ресурсы (закрываете файлы, соединения с БД).

Пример в контексте ASP.NET Core

В современных приложениях глобальная обработка часто выносится в миддлвары (app.UseExceptionHandler), но локальный catch незаменим для точечной бизнес-логики:

[HttpPost]
public async Task<IActionResult> ProcessOrder([FromBody] OrderDto order)
{
    try
    {
        // Валидация, преобразование DTO в доменную модель
        var domainOrder = _mapper.Map<Order>(order);

        // Вызов доменного сервиса
        var result = await _orderService.ProcessOrderAsync(domainOrder);

        return Ok(result);
    }
    catch (ValidationException ex) // Ожидаемое бизнес-исключение
    {
        // Возвращаем 400 с деталями ошибок валидации
        return BadRequest(new { Errors = ex.Errors });
    }
    catch (DomainException ex) // Другое бизнес-исключение
    {
        // Возвращаем 409 Conflict или другой осмысленный статус
        return Conflict(new { Message = ex.Message });
    }
    catch (Exception ex) // Все неожиданное
    {
        // Логируем и возвращаем 500, не раскрывая внутренностей
        _logger.LogCritical(ex, "Критическая ошибка при обработке заказа.");
        return StatusCode(500, "Внутренняя ошибка сервера");
    }
}

Итог: Блок catch — это инструмент для контролируемого управления ошибками, а не их игнорирования. Его грамотное применение, основанное на конкретности, логировании и преобразовании исключений, напрямую влияет на надежность и сопровождаемость backend-приложения.

Как применяешь catch? | PrepBro