Обработка ошибок в C#: try/catch и продвинутые техники для .NET

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Продвинутая обработка ошибок в C#: от базового try/catch до промышленных паттернов

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

1. Фильтры исключений: точность вместо заглушек

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

try
{
    // Код, который может выбросить исключение
    var result = ProcessData(input);
}
catch (CustomException ex) when (ex.ErrorCode == 1001)
{
    // Обрабатываем только ошибки с кодом 1001
    Log.Error($"Критическая ошибка: {ex.Message}");
    throw; // Пробрасываем дальше, если нужно
}
catch (CustomException ex) when (ex.IsRecoverable)
{
    // Восстанавливаемая ошибка — логируем и продолжаем
    Log.Warning($"Восстановимая ошибка: {ex.Message}");
    RetryOperation();
}

Преимущества:

  • Не требуется повторный throw для фильтрации.
  • Улучшается читаемость и производительность (стек вызовов не портится).
  • Можно комбинировать несколько блоков catch с разными условиями.

2. Паттерн "Возобновление" (Retry) и Polly

Не все ошибки фатальны. Для временных сбоев (сетевые проблемы, блокировки БД) используйте паттерн повторных попыток. Встроенные средства C# ограничены, поэтому рекомендуется библиотека Polly.

using Polly;
using Polly.Retry;

// Определяем политику повторных попыток
AsyncRetryPolicy retryPolicy = Policy
    .Handle<HttpRequestException>() // Обрабатываем только HTTP ошибки
    .WaitAndRetryAsync(
        3, // Количество попыток
        retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)), // Экспоненциальная задержка
        onRetry: (exception, timeSpan, retryCount, context) =>
        {
            Log.Warning($"Попытка {retryCount} не удалась. Повтор через {timeSpan.Seconds} сек.");
        });

// Применяем политику
await retryPolicy.ExecuteAsync(async () =>
{
    var response = await httpClient.GetAsync("https://api.example.com/data");
    response.EnsureSuccessStatusCode();
});

Важно: Не используйте бесконечные ретраи. Всегда задавайте максимальное количество попыток и используйте Circuit Breaker для защиты системы от каскадных отказов.

3. Правильная передача исключений: throw vs throw ex

Одна из самых частых ошибок — потеря стека вызовов. Разница между throw и throw ex критична для отладки.

try
{
    // ...
}
catch (Exception ex)
{
    // НЕПРАВИЛЬНО: теряем оригинальный стек
    // throw ex;  

    // ПРАВИЛЬНО: сохраняем стек
    throw;

    // ИЛИ: добавляем контекст, не теряя оригинал
    // throw new ApplicationException("Ошибка в модуле X", ex);
}

Если нужно добавить контекст, используйте ExceptionDispatchInfo (из пространства System.Runtime.ExceptionServices) для точного сохранения стека при перебросе между потоками.

ExceptionDispatchInfo.Capture(exception).Throw(); // Сохраняет оригинальный стек

4. Пользовательские исключения и контекст ошибки

Стандартные исключения часто не несут бизнес-контекста. Создавайте свои классы исключений с дополнительными свойствами.

public class PaymentProcessingException : Exception
{
    public int OrderId { get; }
    public decimal Amount { get; }
    public PaymentErrorType ErrorType { get; }

    public PaymentProcessingException(string message, int orderId, decimal amount, PaymentErrorType errorType, Exception inner = null)
        : base(message, inner)
    {
        OrderId = orderId;
        Amount = amount;
        ErrorType = errorType;
    }

    public override string ToString()
    {
        return $"Заказ #{OrderId}: {Message} (Сумма: {Amount:C}, Тип: {ErrorType})";
    }
}

// Использование
throw new PaymentProcessingException("Недостаточно средств", 12345, 1500.00m, PaymentErrorType.InsufficientFunds);

Best practices:

  • Всегда наследуйте от Exception (или ApplicationException в legacy коде).
  • Добавляйте сериализацию ([Serializable]) для поддержки AppDomain.
  • Не делайте исключения слишком общими — каждый тип должен иметь четкий смысл.

5. Глобальная обработка и логирование в ASP.NET Core

В веб-приложениях никогда не полагайтесь только на локальные try/catch. Используйте middleware для централизованной обработки.

// Middleware для глобальной обработки исключений
public class ExceptionMiddleware
{
    private readonly RequestDelegate _next;
    private readonly ILogger<ExceptionMiddleware> _logger;

    public ExceptionMiddleware(RequestDelegate next, ILogger<ExceptionMiddleware> logger)
    {
        _next = next;
        _logger = logger;
    }

    public async Task InvokeAsync(HttpContext context)
    {
        try
        {
            await _next(context);
        }
        catch (PaymentProcessingException ex)
        {
            _logger.LogWarning(ex, "Ошибка обработки платежа");
            context.Response.StatusCode = 400;
            await context.Response.WriteAsJsonAsync(new { error = ex.Message, orderId = ex.OrderId });
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Необработанная ошибка");
            context.Response.StatusCode = 500;
            await context.Response.WriteAsJsonAsync(new { error = "Внутренняя ошибка сервера" });
        }
    }
}

// Регистрация в Program.cs
app.UseMiddleware<ExceptionMiddleware>();

Дополнительно: Используйте IExceptionHandler (новинка .NET 8) для более чистой архитектуры.

6. Паттерн Result вместо исключений (Functional Error Handling)

В некоторых сценариях (например, валидация ввода) исключения слишком тяжеловесны. Расс

Блоги

Рекомендаций по книгам