try catch 块中的 "when" 关键字是否与 if 语句相同?

Is the "when" keyword in a try catch block the same as an if statement?

在 C# 6.0 中引入了 "when" 关键字,现在您可以在 catch 块中过滤异常。但这与 catch 块中的 if 语句不一样吗?如果是这样,这不只是语法糖还是我遗漏了什么?

例如带有 "when" 关键字的 try catch 块:

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) {…}

But isn't this the same as a if statement inside a catch block?

不,因为如果 ex.Status== WebExceptionStatus.SendFailure,没有 when 的第二种方法将无法达到第二种 Catch。使用 when 将跳过第一个 Catch

所以在没有 when 的情况下处理 Status 的唯一方法是将逻辑放在一个 catch:

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

else throw 将确保此处仅处理 WebExceptionsstatus=TimeoutSendFailure,类似于 when 方法。所有其他的都不会被处理,异常将被传播。请注意,它不会被最后的 Catch 捕获,因此与 when 还是有区别的。这显示了 when.

的优点之一

扩展 Tim 的答案。

C# 6.0 引入了新功能异常筛选器和新关键字 when

Is the "when" keyword in a try catch block the same as a if statement?

when 关键字的作用类似于 if。 when 条件是谓词表达式,可以附加到 catch 块。如果谓词表达式被评估为真,则执行关联的 catch 块;否则,catch 块将被忽略。

C# 6.0 Exception Filter and when Keyword

上给出了精彩的解释

isn't this the same as a if statement inside a catch block?

没有。 为了异常抛出系统的利益,它更像是一个 "discriminator"。

还记得异常是如何被抛出两次的吗?

第一个 "throw"(Studio 继续讨论的那些 "first-chance" 异常)告诉 运行- 定位 最近的 可以处理这种 Type 异常并收集 "here" 和 "there" 之间的任何 "finally" 块的异常处理程序。

第二个 "throw" 展开调用堆栈,依次执行每个 "finally" 块,然后将执行引擎传递到定位的异常处理代码的入口点。

以前,我们只能区分异常的不同类型。这个装饰器给了我们更细粒度控制,只捕获碰巧处于我们状态的特定异常类型可以做点什么.
例如(凭空)你可能想要处理一个 "Database Exception" 表示连接断开,当这种情况发生时,尝试自动重新连接。
很多 数据库操作抛出 "Database Exception",但您只对其中的特定 "Sub-Type" 感兴趣,基于在 Exception 对象的 properties 上,所有这些都可用于异常抛出系统。

catch 块中的 "if" 语句 达到相同的最终结果,但在 运行 时会 "cost" 更多.因为这个块将捕获 any 和所有 "Database Exceptions",它会为其中的 all 调用,即使它只能做一些事情对[非常]一小部分人有用。这也意味着你必须重新抛出 [所有] 你 不能 做任何有用的异常,这只是重复整个,两次通过,处理程序查找,最后再次收获,抛出异常。

类比:一个[非常奇怪]的收费桥。

默认情况下,您必须 "catch" 每辆车才能支付通行费。 如果,比如说,城市雇员驾驶的汽车 免除 通行费(我 确实 说这很奇怪),那么你只需要停车由其他任何人驱动。

你可以停下辆车然后问:

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

或者,如果您在汽车接近时对 "interrogating" 有所了解,那么您可以 忽略 那些由城市雇员驾驶的车辆,例如:

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

除了您在这里已有的几个很好的答案之外:异常过滤器和 catch 块中的 "if" 之间存在 非常重要的区别在内部 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。 运行time 检查是否有任何 catch 块可以处理它,确实有。最后块 运行s CleanItUp()。烂摊子被清理干净了。控制传递给 catch 块。 catch 块调用 F(),然后可能调用 C()。

M2呢?它调用 N(),后者调用 MakeAMess()。弄得一团糟。然后 DoSomethingDangerous() 抛出 MyException。 运行time 会检查是否有任何 catch 块可以处理它,也许是。 运行time 调用 F() 看 catch 块是否可以处理它,它可以。 finally 块 运行s CleanItUp(),控制传递给 catch,并调用 C()。

你注意到区别了吗?在 M1 情况下,F() 在清理混乱之后 被调用,而在 M2 情况下,它在之前 被调用清理干净。如果 F() 的正确性取决于没有混乱,那么如果您将 M1 重构为看起来像 M2,您就会遇到大麻烦!

这里不仅仅是正确性问题;也有安全隐患。假设我们正在做的"mess"是"impersonate the administrator",危险操作需要管理员权限,清理不冒充管理员。在M2中,对F的调用具有管理员权限。在 M1 中没有。假设用户向包含 M2 的程序集授予了很少的权限,但 N 处于完全信任的程序集中; M2 程序集中的潜在恶意代码可以通过这种诱人攻击获得管理员访问权限。

作为练习:你会如何编写 N 来抵御这种攻击?

(当然 运行time 足够聪明,可以知道是否有 堆栈注释 授予或拒绝 M2 和 N 之间的特权,并且它会恢复之前的那些调用 F。这是 运行时间造成的混乱,它知道如何正确处理它。但是 运行时间不知道 制作。)

这里的关键要点是,任何时候你处理异常,根据定义,都会出现严重错误,世界并不像你想象的那样。 异常过滤器的正确性不得依赖于异常条件所违反的不变量。

更新:

Ian Ringrose 问我们是怎么陷入这个困境的。

这部分答案将有些推测,因为这里描述的一些设计决策是在我 2012 年离开 Microsoft 之后做出的。但是我已经与语言设计者聊天关于这些问题很多次,我想我可以给出一个公平的情况总结。

在 finally 块之前制作过滤器 运行 的设计决定是在 CLR 的早期做出的;询问您是否想要该设计决策的小细节的人是 Chris Brumme。 (更新:遗憾的是,Chris 不再可以回答问题。)他曾经有一个博客,其中详细解释了异常处理模型,但我不知道它是否还在互联网上。

这是一个合理的决定。出于调试目的,我们想知道 before finally 块 运行 是否要处理此异常,或者我们是否处于 "undefined behaviour" 场景完全未处理的异常破坏了进程。因为如果程序在调试器中 运行ning,那么未定义的行为将包括在未处理的异常 before finally 块 运行 之前中断。

CLR 团队很好地理解了这些语义引入安全性和正确性问题的事实;事实上,我在我的第一本书中讨论过它,这本书在很多年前就已经出版了,十二年前在我的博客上:

https://blogs.msdn.microsoft.com/ericlippert/2004/09/01/finally-does-not-mean-immediately/

即使 CLR 团队愿意,现在也将是对 "fix" 语义的重大更改。

该特性在CIL和VB.NET中一直存在,攻击者通过过滤器控制代码的实现语言,因此将该特性引入C#并没有增加任何新的攻击面。

事实上,这个引入安全问题的功能已经 "in the wild" 几十年了,据我所知,这从来没有成为严重安全问题的原因,这证明它不是一个非常有效的途径对于攻击者。

为什么 VB.NET 的第一个版本中包含此功能,并花了十多年时间才将其纳入 C#?嗯,"why not" 这样的问题很难回答,但在这种情况下,我可以很容易地总结一下:(1) 我们脑子里还有很多其他事情,(2) Anders 觉得这个功能没有吸引力. (而且我对此也不感到兴奋。)这使它多年来一直处于优先列表的底部。

它是如何让它在优先级列表中足够高以在 C# 6 中实现的?很多人都要求这个功能,这总是有利于这样做的。 VB 已经拥有它,并且 C# 和 VB 团队希望尽可能以合理的成本实现对等,所以这也是要点。但最大的转折点是:在 Roslyn 项目本身 中有一个场景,其中异常过滤器会非常有用。 (我不记得它是什么;如果你想找到它并报告回来,请深入研究源代码!)

作为语言设计者和编译器编写者,您要小心不要优先考虑编译器编写者的生活的功能更轻松;大多数 C# 用户不是编译器编写者,他们是客户!但最终,拥有该功能有用的真实场景的集合,包括一些激怒编译器团队本身的场景,打破了平衡。