Und*_*ade 56 c# try-catch c#-6.0
在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) {…}
Eri*_*ert 73
除了你已经有的几个很好的答案之外:异常过滤器和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.运行时检查是否有任何可以处理它的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团队想要,现在"修复"语义也将是一个突破性的变化.
该功能一直存在于CIL和VB.NET中,攻击者使用过滤器控制代码的实现语言,因此将该功能引入C#不会增加任何新的攻击面.
事实上,引入安全问题的这个功能已经"存在"几十年了,据我所知,从来没有出现严重安全问题的原因证明它对于攻击者来说并不是一个非常富有成效的途径.
为什么然后是VB.NET的第一个版本中的功能并花了十多年的时间才使它成为C#?那么,"为什么不"这样的问题很难回答,但在这种情况下我可以很容易地总结出来:(1)我们脑子里还有很多其他的东西,(2)Anders发现这个特征没有吸引力.(而且我对此也不感兴趣.)这使它多年来一直处于优先级列表的底部.
那么它如何才能在C#6中实现优先级列表呢?很多人都要求这个功能,这一点总是有利于这样做.VB已经拥有它,并且C#和VB团队希望在合理的成本下尽可能保持平价,所以这也是重点.但最重要的转折点是:在Roslyn项目中有一个场景,其中异常过滤器本来就非常有用.(我不记得它是什么;如果你想找到并报告回来,请深入了解源代码!)
由于两种语言设计者和编译器的作家,你要小心,不优先考虑,使该功能只编译器作者的生活更加轻松; 大多数C#用户不是编译器编写者,他们是客户!但最终,拥有一系列真实场景,其中该功能非常有用,包括一些令编辑器团队本身恼火的场景,这些都是平衡的结果.
Tim*_*ter 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=Timeout或SendFailure在这里进行处理,类似when的做法.所有其他人都不会被处理,异常将被传播.请注意,它不会被最后一个捕获Catch,因此仍然存在差异when.这显示了其中一个优点when.
小智 11
这与catch块中的if语句不一样吗?
不会.它更像是一个"鉴别者",有利于抛出异常系统.
还记得Exceptions被抛出两次吗?
第一个"抛出"(那些"第一次偶然"的例子,'Studio继续讨论)告诉运行时找到最近的异常处理程序,它可以处理这种异常类型并收集任何"finally"块之间的"这里和那里".
第二个"throw"展开调用堆栈,依次执行每个"finally"块,然后将执行引擎传递到定位的Exception处理代码的入口点.
以前,我们只能区分不同类型的例外.这个装饰器为我们提供了更细粒度的控制,只捕获了一个特殊的异常类型,它恰好处于我们可以做某事的状态.
例如(凭空)您可能希望处理指示连接断开的"数据库异常",并在发生这种情况时尝试自动重新连接.
许多数据库操作会引发"数据库异常",但您只对它们的特定"子类型" 感兴趣,这些属性基于Exception对象的属性,所有这些属性都可用于异常抛出系统.
catch块中的"if"语句将获得相同的最终结果,但在运行时会"花费"更多.由于此块将捕获的任何和所有的 "数据库异常",它会被调用为所有的人,即使它只能为他们做点什么的[非常]小部分是有用的.这也意味着你必须重新抛出[所有]你不能做任何有用的异常,这只是重复整个,两遍,处理程序发现,最终收获,抛出异常的farago all再次.
打个比方:一个[非常奇怪的]收费桥.
默认情况下,您必须"抓住"每辆车,以便他们支付通行费.比方说,如果城市员工驾驶的汽车免于收费(我确实说它很奇怪),那么你只需要停下其他人驾驶的汽车.
你可以停止所有的车,问:
catch( Car car ) 
{ 
   if ( car.needsToPayToll() ) 
      takePayment( car ); 
} 
或者,如果您在接近时有某种方式"询问"汽车,那么您可以忽略那些由城市员工驱动的汽车,如:
catch( Car car ) when car.needsToPayToll() 
{ 
   takePayment( car ); 
}