В чем разница между throw и throw ex при перепрокидывании исключения в .NET
Что проверяют
Понимаете ли вы, что происходит со стеком вызовов при повторном выбросе и почему от этого зависит, найдёт ли дежурный причину падения по логу. Следом обычно спрашивают, как перебросить исключение, пойманное в другом месте, и как правильно обернуть его в своё.
Как отвечать
Ключевая мысль: объект исключения в обоих случаях один и тот же, разница в свойстве StackTrace.
throw;: перебрасывает текущее исключение, стек сохраняется от исходной точки. В логе виден метод, где ошибка возникла на самом деле;throw ex;: выбрасывает тот же объект заново, и трассировка начинается со строки вcatch. Всё, что было глубже, пропадает. Анализатор предупреждает об этом правиломCA2200;- обёртка:
throw new OrderProcessingException("Не удалось провести заказ", ex);добавляет контекст вашего слоя, а исходное исключение с полным стеком остаётся вInnerException; ExceptionDispatchInfo.Capture(ex).Throw(): когда исключение поймано в одном месте, а выбросить его нужно позже или в другом потоке. Исходный стек сохраняется, новый дописывается следом. На этом механизмеawaitдоставляет исключение из задачи.
Сильный ход: упомянуть фильтры catch (Exception ex) when (Log(ex)), где Log возвращает false. Условие выполняется до раскрутки стека, поэтому ошибку можно залогировать, не перехватывая, а в дампе останется исходное состояние.
Итог: если перебрасываете как есть, пишите throw;. Нужен контекст, оборачивайте с innerException. throw ex; в продуктовом коде не нужен.
Где спотыкаются
Говорят, что throw ex создаёт новое исключение. Нет: тип, сообщение, Data и InnerException те же, перезаписывается только трассировка.
Оборачивают без исходного: throw new ServiceException("Ошибка"). Это хуже throw ex, потому что пропадает не только стек, но и тип с сообщением первопричины.
Логируют и перебрасывают на каждом уровне. Одна ошибка даёт пять записей в логе, и по ним труднее понять, где она началась. Логировать стоит там, где исключение обрабатывается окончательно.