Ответ 1
В дополнение к нескольким тонким ответам, которые у вас уже есть, существует очень важное различие между фильтром исключений и "if" в блоке catch: фильтры запускаются до внутренних блоков finally.
Рассмотрим следующее:
void M1()
{
try { N(); } catch (MyException) { if (F()) C(); }
}
void M2()
{
try { N(); } catch (MyException) when F() { C(); }
}
void N()
{
try { MakeAMess(); DoSomethingDangerous(); }
finally { CleanItUp(); }
}
Порядок вызовов отличается между M1 и M2.
Предположим, что M1 вызывается. Он вызывает N(), который вызывает MakeAMess(). Делается беспорядок. Затем DoSomethingDangerous() выбрасывает MyException. Проверка времени выполнения проверяет, есть ли какой-либо блок catch, который может обрабатывать это, и есть. Блок finally запускает CleanItUp(). Путаница очищается. Управление переходит к блоку catch. И блок catch вызывает F(), а затем, возможно, C().
Как насчет M2? Он вызывает N(), который вызывает MakeAMess(). Делается беспорядок. Затем DoSomethingDangerous() выбрасывает MyException. Проверка времени выполнения проверяет, есть ли какой-либо блок catch, который может обрабатывать это, и есть - возможно. Время выполнения вызывает F(), чтобы увидеть, может ли блок catch обрабатывать его, и может. Блок finally запускает CleanItUp(), управление переходит к catch, и вызывается C().
Вы заметили разницу? В случае M1 F() вызывается после того, как беспорядок очищается, а в случае M2 вызывается до того, как беспорядок очищается. Если F() зависит от отсутствия беспорядка для его правильности, тогда у вас большие проблемы, если вы реорганизуете M1, чтобы выглядеть как M2!
Здесь есть не просто проблемы с правильностью; есть и последствия для безопасности. Предположим, что "беспорядок", который мы создаем, "олицетворяет администратора", опасная операция требует доступа администратора, а очистка не олицетворяет администратора. В M2, вызов F имеет права администратора. В M1 это не так. Предположим, что пользователь предоставил несколько привилегий для сборки, содержащей M2, но N находится в сборке полного доверия; потенциально-враждебный код в сборке M2 может получить доступ администратора через эту приманку.
Как упражнение: как бы вы написали N так, чтобы он защищался от этой атаки?
(Конечно, среда исполнения достаточно умна, чтобы знать, есть ли аннотации стека, которые предоставляют или запрещают привилегии между M2 и N, и возвращают их перед вызовом F. Это беспорядок, который выполнял исполняемый файл, и он знает, как справиться с это правильно, но среда выполнения не знает о каких-либо других беспорядках, которые вы сделали.)
Ключевым выводом здесь является то, что в любое время, когда вы обрабатываете исключение, по определению что-то пошло ужасно неправильно, и мир не так, как вы думаете. Фильтры исключений не должны зависеть от их правильности от инвариантов, которые были нарушены исключительным условием.
ОБНОВЛЕНИЕ:
Ян Ринроуз спрашивает, как мы попали в этот беспорядок.
Эта часть ответа будет несколько гипотетической, поскольку некоторые дизайнерские решения, описанные здесь, были предприняты после того, как я покинул Microsoft в 2012 году. Однако я неоднократно общался с разработчиками языка об этих проблемах, и я думаю, что могу дать справедливое изложение ситуации.
Решение о создании фильтров выполняется до того, как окончательные блоки были сделаны в самые ранние дни CLR; человек, чтобы спросить, хотите ли вы, чтобы мелкие детали этого дизайнерского решения были Крисом Брумме. (ОБНОВЛЕНИЕ: К сожалению, Крис больше не доступен для вопросов.) У него был блог с подробной экзегезой модели обработки исключений, но я не знаю, все еще ли он в Интернете.
Это разумное решение. Для целей отладки, которые мы хотим знать до того, как окончательные блоки будут запущены, будет ли обработано это исключение, или если мы находимся в сценарии "undefined поведения" полностью необработанного исключения, разрушающего процесс. Поскольку, если программа запущена в отладчике, поведение undefined будет включать в себя разрыв в точке необработанного исключения до того, как будут запущены блоки finally.
Тот факт, что эта семантика вводит вопросы безопасности и корректности, была хорошо понята командой CLR; на самом деле я обсуждал это в своей первой книге, которая была отправлена много лет назад и двенадцать лет назад в моем блоге:
https://blogs.msdn.microsoft.com/ericlippert/2004/09/01/finally-does-not-mean-immediately/
И даже если бы команда CLR захотела, это было бы потрясающим изменением для "исправления" семантики.
Эта функция всегда существовала в CIL и VB.NET, а злоумышленник контролирует язык реализации кода с помощью фильтра, поэтому введение этой функции в С# не добавляет никакой новой поверхности атаки.
И тот факт, что эта функция, которая вводит проблему безопасности, была "в дикой природе" в течение нескольких десятилетий и, насколько мне известно, никогда не была причиной серьезной проблемы безопасности, свидетельствует о том, что она не очень плодотворная возможность для нападавших.
Почему тогда была функция в первой версии VB.NET и заняла более десятилетия, чтобы превратить ее в С#? Ну, "почему нет" таких вопросов трудно ответить, но в этом случае я могу сделать это достаточно легко: (1) у нас было много других вещей на нашем уме, и (2) Андерс считает эту функцию непривлекательной. (И я тоже не в восторге от этого.) В течение многих лет это переместило его в список приоритетов.
Как же он сделал его достаточно высоким в списке приоритетов, который будет реализован на С# 6? Многие люди просили эту функцию, которая всегда указывает на это. У VB уже было это, и команды С# и VB хотели иметь паритет, когда это возможно, по разумной цене, так что точки тоже. Но большой переломный момент был: в проекте Roslyn был сценарий, где фильтры исключительности были бы действительно полезны. (Я не помню, что это было: пойдите в исходный код, если вы хотите найти его и отчитаться!)
Как разработчик языка, так и разработчик компилятора, вы должны быть осторожны, чтобы не уделять приоритетное внимание функциям, которые облегчают жизнь писателя-компилятора; большинство пользователей С# не являются компиляторами, и они являются клиентами! Но в конечном счете, имея набор реальных сценариев, где эта функция полезна, в том числе некоторые, которые раздражали команду компилятора, уравновешивали баланс.