Урок по треку C# и .NET 6 мин

Исключения

try, catch, finally, throw против throw ex, фильтры when, свои исключения и using.

Исключения в .NET это механизм для неожиданных ситуаций. На собесе проверяют синтаксис (throw против throw ex, finally, фильтры) и стратегию: где ловить и что отдавать клиенту.

try, catch, finally

try
{
    Process();
}
catch (InvalidOperationException ex) when (ex.Message.Contains("lock"))
{
    // фильтр when: условие проверяется до раскрутки стека
}
catch (Exception ex)
{
    logger.LogError(ex, "Обработка упала");
    throw;               // сохраняет стек
}
finally
{
    Cleanup();           // выполнится при любом выходе
}

Блоки catch проверяются сверху вниз, специфичные пишут выше общих (иначе ошибка компиляции). finally выполняется даже после return, но не при Environment.FailFast, убийстве процесса и переполнении стека.

throw или throw ex

throw; пробрасывает исключение с исходным стеком. throw ex; начинает стек заново с текущей строки, и место настоящей ошибки теряется. Чтобы добавить контекст, бросайте новое исключение с внутренним: throw new OrderException("...", ex);.

Двухпроходная обработка

Сначала среда ищет подходящий catch, вычисляя фильтры when, и только потом раскручивает стек, выполняя finally. Поэтому фильтр, вернувший false, не трогает исключение вовсе, и дамп сохраняет исходное состояние. Трюк: catch (Exception e) when (Log(e)) с Log, возвращающим false, логирует без перехвата.

using и IDisposable

using разворачивается в try/finally с Dispose. Удобная форма: using var file = File.OpenRead(path); до конца блока. Для асинхронных ресурсов await using.

Какие исключения бросать

  • ArgumentException, ArgumentNullException.ThrowIfNull(x), ArgumentOutOfRangeException: плохие аргументы.
  • InvalidOperationException: объект в неподходящем состоянии.
  • NotSupportedException, ObjectDisposedException, OperationCanceledException.

Свои исключения наследуйте от Exception (не от ApplicationException) с суффиксом Exception и стандартными конструкторами.

Исключения и производительность

Бросок исключения дорогой: объект, сбор стека, поиск обработчика. Для ожидаемых ситуаций (валидация, парсинг) используйте паттерн TryParse или объект результата. В .NET 9 обработка исключений заметно ускорена, но правило остаётся.

Исключения в async

await пробрасывает исключение задачи как есть. У Task.WhenAll в Exception.InnerExceptions лежат все ошибки, но await бросит только первую. .Result и .Wait() оборачивают исключения в AggregateException. Исключение из async void уходит в контекст синхронизации и может уронить процесс.

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

builder.Services.AddProblemDetails();
builder.Services.AddExceptionHandler<AppExceptionHandler>(); // .NET 8, IExceptionHandler
app.UseExceptionHandler();

Обработчик маппит типы исключений в HTTP-статусы и отдаёт ProblemDetails без стека, а полную информацию пишет в лог с trace id.

Частые ошибки

  • catch (Exception) { }: проглоченная ошибка.
  • throw ex; и потерянный стек.
  • Исключения для управления потоком в горячем коде.
  • Ловить AggregateException вокруг await.

Что спросят на собесе

  • Чем throw; отличается от throw ex;?
  • Когда не выполнится finally?
  • Что такое фильтры исключений и чем они лучше if внутри catch?
  • Как устроить обработку ошибок в веб-API?
  • Что происходит с исключениями в Task.WhenAll?

Закрепите за 5 минут

После урока в тренажёре 3-5 проверочных вопросов, дальше модуль уходит в повторения.