Подтвердить что ты не робот

Является ли ключевое слово "когда" в блоке catch try таким же, как и оператор if?

В С# 6.0 было введено ключевое слово "когда", теперь вы можете фильтровать исключение в блоке catch. Но разве это не так, как оператор if внутри блока catch? если да, то разве это не просто синтаксический сахар или я что-то не хватает?

Например, блок catch try с ключевым словом "когда":

try { … } 
catch (WebException ex) when ex.Status == WebExceptionStatus.Timeout {
   //do something
}
catch (WebException ex) when ex.Status== WebExceptionStatus.SendFailure {
   //do something
}
catch (Exception caught) {…}

или

try { … } 
catch (WebException ex) {
   if(ex.Status == WebExceptionStatus.Timeout) {
      //do something
   }
}
catch (WebException ex) {
   if(ex.Status == WebExceptionStatus.SendFailure) {
      //do something
   }
}
catch (Exception caught) {…}
4b9b3361

Ответ 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 был сценарий, где фильтры исключительности были бы действительно полезны. (Я не помню, что это было: пойдите в исходный код, если вы хотите найти его и отчитаться!)

Как разработчик языка, так и разработчик компилятора, вы должны быть осторожны, чтобы не уделять приоритетное внимание функциям, которые облегчают жизнь писателя-компилятора; большинство пользователей С# не являются компиляторами, и они являются клиентами! Но в конечном счете, имея набор реальных сценариев, где эта функция полезна, в том числе некоторые, которые раздражали команду компилятора, уравновешивали баланс.

Ответ 2

Но разве это не так, как оператор if внутри блока catch?

Нет, потому что ваш второй подход без when не достигнет второго Catch, если ex.Status== WebExceptionStatus.SendFailure. С when первый Catch был бы пропущен.

Таким образом, единственным способом обработки Status без when является логика в одном Catch:

try { … } 
catch (WebException ex) {
   if(ex.Status == WebExceptionStatus.Timeout) {
      //do something
   }
   else if(ex.Status == WebExceptionStatus.SendFailure) {
      //do something
   }
   else
      throw; // see Jeppe comment 
}
catch (Exception caught) {…}

else throw гарантирует, что здесь обрабатываются только WebExceptions с помощью status=Timeout или SendFailure, аналогично подходу when. Все остальные не будут обработаны, и исключение будет распространено. Обратите внимание, что он не будет улавливаться последним Catch, поэтому разница с when остается. Это показывает одно из преимуществ when.

Ответ 3

не совпадает ли это с оператором if внутри блока catch?

Нет. Он действует скорее как "дискриминатор" в интересах системы выброса исключений.

Помните, как исключение выбрасывается дважды?

Первый "бросок" (те "первые шансы" Исключения, о которых говорит "Студия" ), указывают Run-Time на поиск ближайшего обработчика исключений, который может справиться с этим типом исключения и собирать все "наконец", блоки между "здесь" и "там".

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

Раньше мы могли различать только разные типы исключений. Этот декоратор дает нам более тонкий контроль над зерном, но только улавливает особый тип исключения, который находится в состоянии , о котором мы можем что-то сделать.
Например, (из-под эфира) вы, возможно, захотите обработать "Исключение базы данных", которое указывает на сломанное соединение, и, когда это произойдет, попробуйте снова подключиться.
Многие операции с базой данных вызывают "Исключение базы данных", но вас интересует только конкретный "подтип" из них, основанный на свойствах объекта Exception, все из которых доступны для системы исключения.

Оператор if if внутри блока catch достигнет того же конечного результата, но он будет "стоить" больше во время выполнения. Поскольку этот блок поймает все и все "Исключения базы данных", он будет вызываться для всех из них, даже если он может сделать только что-то полезное для [очень] небольшой части. Это также означает, что вам нужно повторно выбрасывать [все] Исключения, с которыми вы не можете справиться, что просто повторяет весь, двухпроходный, обработчик, окончательный сбор урожая, исключение-бросание farago all снова.

Аналогия: [очень странный] платный мост.

По умолчанию вам нужно "поймать" каждый автомобиль, чтобы они платили пошлину. Если, скажем, автомобили, управляемые городскими служащими, освобождаются от пошлины (я действительно сказал, что это странно), тогда вам нужно остановить машины, управляемые кем-то еще.

Вы можете остановить каждый автомобиль и спросить:

catch( Car car ) 
{ 
   if ( car.needsToPayToll() ) 
      takePayment( car ); 
} 

Или, если у вас был какой-то способ "допроса" автомобиля по мере приближения к нему, вы могли бы игнорировать тех, кого управляли городские служащие, например:

catch( Car car ) when car.needsToPayToll() 
{ 
   takePayment( car ); 
} 

Ответ 4

Расширение ответа на Tim.

В С# 6.0 представлен новый фильтр исключений функций и новое ключевое слово , когда.

Является ли ключевое слово "когда" в блоке catch try таким же, как и оператор if?

Когда ключевое слово работает как if. A, когда условие является предикатным выражением, которое может быть добавлено к блоку catch. Если выражение предиката оценивается как истинное, выполняется соответствующий блок catch; в противном случае блок catch игнорируется.

Замечательное объяснение дано на Фильтр исключений С# 6.0 и ключевого слова