Исключения в .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?