Ответ
Правильная обработка исключений — это не просто try-catch, а стратегия, которая делает код надежным и удобным для отладки.
1. Базовый синтаксис и рекомендации:
try
{
// Код, который может выбросить исключение
var data = File.ReadAllText("config.json");
var config = JsonSerializer.Deserialize<Config>(data);
}
catch (FileNotFoundException ex) // Ловите конкретные исключения
{
_logger.LogWarning(ex, "Файл конфигурации не найден, используются значения по умолчанию.");
// Восстановление: создаем конфиг по умолчанию
config = Config.Default;
}
catch (JsonException ex) // Обработка ошибок десериализации
{
_logger.LogError(ex, "Неверный формат JSON в конфигурационном файле.");
// Невосстановимая ошибка — пробрасываем выше
throw new InvalidOperationException("Ошибка загрузки конфигурации", ex);
}
finally
{
// Код для освобождения ресурсов (файлов, сетевых подключений)
// Выполняется ВСЕГДА, даже если было исключение или return.
}
2. Ключевые принципы:
- Не ловите то, что не можете обработать: Пусть исключение всплывает до уровня, где есть достаточно контекста для его корректоной обработки (например, контроллер API вернет
500 Internal Server Error). - Используйте конкретные типы исключений в
catch:catch (Exception)должен быть крайней мерой, обычно на верхнем уровне приложения (например, в middleware). - Всегда логируйте исключения: Используйте
ILogger, передавая само исключение как параметр, чтобы сохранить стектрейс. - Избегайте «пустых» catch-блоков (
catch {}): Это скрывает ошибки и усложняет отладку.
3. Создание пользовательских исключений: Создавайте их для ошибок предметной области (business logic).
public class InsufficientFundsException : Exception
{
public decimal CurrentBalance { get; }
public decimal RequiredAmount { get; }
public InsufficientFundsException(decimal currentBalance, decimal requiredAmount)
: base($"Недостаточно средств. Текущий баланс: {currentBalance}, требуется: {requiredAmount}")
{
CurrentBalance = currentBalance;
RequiredAmount = requiredAmount;
}
}
// Использование:
throw new InsufficientFundsException(balance, amount);
4. Глобальная обработка в ASP.NET Core: Используйте middleware для централизованной обработки.
// В Program.cs
app.UseExceptionHandler(appError =>
{
appError.Run(async context =>
{
context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
context.Response.ContentType = "application/json";
var exception = context.Features.Get<IExceptionHandlerFeature>()?.Error;
// Логируем исключение
_logger.LogError(exception, "Необработанное исключение");
// Возвращаем клиенту безопасный ответ (без деталей в продакшене)
await context.Response.WriteAsJsonAsync(new { error = "Произошла внутренняя ошибка." });
});
});
5. Исключения и асинхронный код:
В асинхронных методах исключения «заворачиваются» в AggregateException при использовании Task.Wait() или Task.Result. Всегда используйте await, который пробрасывает исходное исключение.
// ПЛОХО: Получите AggregateException
Task.Run(() => throw new InvalidOperationException()).Wait();
// ХОРОШО: Получите InvalidOperationException
await Task.Run(() => throw new InvalidOperationException());