В чем разница между throw и throw ex в C#?

Ответ

Ключевое отличие — в сохранении оригинальной трассировки стека (stack trace), что критично для отладки.

  • throw; — повторно выбрасывает перехваченное исключение, сохраняя полный стек вызовов.
  • throw ex; — выбрасывает то же исключение заново, но сбрасывает точку возникновения на текущую строку, обрезая стек вызовов.

Пример, демонстрирующий проблему:

void InnerMethod() {
    throw new InvalidOperationException("Ошибка на низком уровне");
}

void OuterMethod() {
    try {
        InnerMethod();
    }
    catch (Exception ex) {
        // ПЛОХО: стек будет указывать на эту строку, а не на InnerMethod
        // throw ex;

        // ХОРОШО: стек сохранит путь: OuterMethod -> InnerMethod
        throw;
    }
}

Когда что использовать:

  1. Всегда используйте throw; при повторной генерации того же исключения в блоке catch. Это стандартная практика.
  2. throw ex; практически никогда не нужен. Его единственное гипотетическое применение — намеренное сокрытие внутренней структуры кода (security through obscurity), что является антипаттерном.
  3. Если нужно создать новое исключение с дополнительным контекстом, оберните оригинальное:
    catch (Exception ex) {
    throw new ApplicationException("Не удалось обработать запрос", ex); // ex становится InnerException
    }

Ответ 18+ 🔞

А, слушай, тут есть одна тонкость, про которую многие нихуя не знают, а потом орут, что отладчик пиздит. Всё дело в том, как ты кидаешь исключение обратно.

Смотри, есть два способа: throw; и throw ex;. И разница — это пиздец какая важная, потому что от неё зависит, увидишь ты, где ошибка вылезла на самом деле, или будешь ебаться как слепой котёнок.

  • throw; — это как сказать: «Слушай, вот эта ошибка, она не моя, я её просто поймал и передаю дальше, как есть». И самое главное — весь стек вызовов, вся цепочка, кто кого вызывал, остаётся нетронутой. Отладчик тебе красиво покажет: вот тут накосячили, вот откуда пошло, вот как докатились до жизни такой.
  • throw ex; — а вот это уже подстава. Ты как бы говоришь: «О, ошибка! Ну-ка, я её перевыброшу, но от моего имени!». И в этот момент весь предыдущий стек вызовов нахуй стирается. Отладчик покажет, что исключение родилось прямо в этой строчке с throw ex;. А где оно реально возникло? Хуй его знает. Ище потом, блядь.

Вот, смотри на примере, как это выглядит:

void InnerMethod() {
    // Допустим, тут реальная проблема
    throw new InvalidOperationException("Ошибка на низком уровне");
}

void OuterMethod() {
    try {
        InnerMethod();
    }
    catch (Exception ex) {
        // ПЛОХО, НЕ ДЕЛАЙ ТАК: стек будет указывать сюда, на строчку ниже. Будто ошибка здесь.
        // Ты реальный источник потеряешь. Зачем тебе эта головная боль?
        // throw ex;

        // ХОРОШО, ДЕЛАЙ ТАК: стек сохранит всю правду — OuterMethod -> InnerMethod.
        // Поймёшь, где корень зла.
        throw;
    }
}

Так когда что юзать, чтобы не быть мудаком?

  1. В 99.9% случаев, когда ловишь и снова кидаешь то же самое исключение — юзай throw;. Это святое правило, запомни его, как «отвернись — плюнь». Сохраняешь всю историю, все логи, отладчик тебе друг.
  2. throw ex; — это говно, не нужен он почти никогда. Разве что ты параноик и хочешь специально спрятать внутренности своей программы (security through obscurity), но это, прости, тупой антипаттерн. Не выёбывайся.
  3. Если поймал ошибку и хочешь добавить от себя пояснение — не перекидывай старое, оберни его в новое! Сделай его InnerException. Тогда и контекст твой добавится, и оригинальная трассировка не потеряется.
catch (Exception ex) {
    // Вот так — культурно, информативно, и стек не похерится.
    throw new ApplicationException("Не удалось обработать запрос, чувак", ex); // ex уходит во InnerException
}

Короче, запомни: throw ex — это как подменить труп в морге. Следы все затереть. Не делай так, если не хочешь потом самому себе глаза выколоть при отладке.