是"何时" try catch块中的关键字与if语句相同吗?

时间:2016-09-15 08:59:04

标签: c# try-catch c#-6.0

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

例如,使用"当"关键字:

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

4 个答案:

答案 0 :(得分:72)

除了你已经拥有的几个很好的答案之外:异常过滤器和&#34之间存在非常重要的区别;如果"在一个catch块中:过滤器在内部最终阻止之前运行

请考虑以下事项:

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之前它还原那些。那是一团糟运行时所做的并且它知道如何正确处理它。但运行时并不知道所做的任何其他混乱。)

这里的关键点在于,无论何时处理异常,根据定义,某些事情都会出现严重错误,并且世界并不像您认为的那样。 异常过滤器不得依赖于异常条件违反的不变量的正确性。

更新:

Ian Ringrose问我们是如何陷入这种混乱的。

这部分答案将有点推测,因为这里描述的一些设计决定是在我于2012年离开微软后进行的。但是我已经与语言设计师就这些问题进行了交谈很多次,我想我可以对这种情况作出公正的总结。

在CLR的早期阶段,在最终块之前运行过滤器的设计决策;询问您是否想要该设计决定的细节的人将是Chris Brumme。 (更新:遗憾的是,克里斯不再有问题了。)他曾经有一个博客,详细解释了异常处理模式,但我不知道它是否还在互联网上。

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

CLR团队非常了解这些语义引入安全性和正确性问题的事实;事实上,我在我的第一本书中讨论了这个问题,这本书很多年前发布,十二年前发布在我的博客上:

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

即使CLR团队想要,也会对#34;修复"进行彻底改变。现在的语义。

该功能一直存在于CIL和VB.NET中,攻击者使用过滤器控制代码的实现语言,因此将该功能引入C#不会增加任何新的攻击面。

事实上,引入安全问题的这个功能已经在野外发布了#34;现在几十年来,据我所知,从来没有出现严重安全问题的原因证明它对攻击者来说并不是一个非常富有成效的途径。

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

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

作为语言设计师和编译器编写者,您需要小心优先考虑使编译器编写者的生活更轻松的功能;大多数C#用户不是编译器编写者,他们是客户!但最终,拥有一系列真实场景,其中该功能非常有用,包括一些令编辑器团队本身恼火的场景,这些都是平衡的结果。

答案 1 :(得分:46)

  

但是这与catch块中的if语句相同吗?

不,因为如果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's comment 
}
catch (Exception caught) {…}

else throw将确保此处仅处理WebExceptions status=TimeoutSendFailure when,类似于Catch方法。所有其他人都不会被处理,异常将被传播。请注意,它不会被最后when抓住,因此when仍然存在差异。这显示了table-striped的一个优点。

答案 2 :(得分:11)

  

这与catch块中的if语句相同吗?

没有。 它更像是一个“鉴别者”。为了抛出异常系统的好处。

还记得如何抛出两次吗?

第一个" throw" (那些"第一次机会"' Studio继续讨论的例外情况)告诉运行时找到可以处理此类型的最近的异常处理程序的例外并收集任何"最后"在"这里"之间的区块和"那里"。

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

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

" if" catch块中的语句将实现相同的最终结果,但它将成本"成本"更多在运行时。因为这个块将捕获任何和所有"数据库异常",它将被调用所有,即使它只能做一些有用的东西其中很少一部分。这也意味着你必须重新抛出[所有]你无法执行任何有用的异常,这只是重复整个,两遍,处理程序查找,终于收获,异常抛出的farago了。

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

默认情况下,你必须"赶上"每辆车都要为他们支付通行费。 例如,如果由城市员工驾驶的汽车从收费中免除(我确实说这很奇怪),那么您只需要停止由其他人驾驶的汽车。

您可以停止每辆汽车并询问:

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

或者,如果你有某种方式"询问"当汽车接近时,您可以忽略由城市员工驱动的汽车,如:

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

答案 3 :(得分:0)

延伸答案蒂姆。

C#6.0引入了新的功能异常过滤器,并在时引入了新的关键字

  

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

when关键字的工作方式与if相同。 when条件是谓词表达式,可以附加到catch块。如果谓词表达式被评估为真,则执行关联的catch块;否则,忽略catch块。

C# 6.0 Exception Filter and when Keyword

给出了一个很好的解释