try语句中的“when”关键字与if语句是否相同?
在C#6.0中引入了“when”关键字,现在您可以在catch块中过滤exception了。 但是这不是一个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) {…}
除了这里已经有的几个很好的答案之外,catch块中的exceptionfilter和“if”之间还有一个非常重要的区别 : filter在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(),调用MakeMess()。 一团糟。 然后DoSomethingDangerous()抛出MyException。 运行时检查是否有任何catch块可以处理,并有。 finally块运行CleanItUp()。 混乱被清理。 控制权传递给catch块。 而catch块调用F(),然后,也许,C()。
M2呢? 它调用N(),调用MakeMess()。 一团糟。 然后DoSomethingDangerous()抛出MyException。 运行时检查,看看是否有任何catch块可以处理,并有 – 也许。 运行时调用F()来查看catch块是否可以处理它,也可以。 finally块运行CleanItUp(),控制传递给catch,并调用C()。
你有没有注意到这个区别? 在M1情况下, 在清理垃圾之后调用F(),在M2清理之前调用它。 如果F()取决于它的正确性没有混乱,那么如果你重构M1来看起来像M2,那么你将面临很大的麻烦!
这里不仅仅是正确性问题, 还有安全隐患。 假设我们正在制作的“烂摊子”是“冒充pipe理员”,危险的操作需要pipe理员访问,并且清理非模拟pipe理员。 在M2中,对F的调用具有pipe理员权限 。 在M1它不。 假设用户授予了包含M2的程序集很less的权限,但是N是完全信任的程序集; M2的程序集中潜在的恶意代码可以通过这个引诱攻击获得pipe理员访问权限。
作为一个练习:你会如何写N来防御这种攻击?
(当然,运行时足够聪明,可以知道在M2和N之间是否存在批注或拒绝特权的堆栈注释 ,并且在调用F之前将这些特权还原。这是运行时造成的混乱,并且知道如何正确处理它。但运行时不知道你做的任何其他的混乱。)
这里关键的一点是,无论什么时候你处理一个例外,根据定义,有些事情是非常糟糕的,而且这个世界并不像你认为的那样。 exceptionfilter不能依赖于由exception条件侵犯的不variables的正确性。
更新:
Ian Ringrose问我们如何陷入混乱。
这部分答案会有些猜测,因为这里描述的一些devise决定是在2012年离开微软之后进行的。但是我多次与语言devise师讨论这些问题,我想我可以对情况。
在CLR的早期阶段就采取了使滤波器在最终阻止之前运行的devise决策; 要问你是否想要这个devise决定的小细节的人是Chris Brumme。 (更新:令人遗憾的是,克里斯不再有问题。)他曾经有一个博客,详细解释了exception处理模型,但是我不知道它是否还在互联网上。
这是一个合理的决定。 出于debugging的目的,我们希望在finally块运行之前知道这个exception是否将被处理,或者如果我们处于“未定义的行为”场景中的一个完全未处理的exception破坏过程。 因为如果程序在debugging器中运行,那么这个未定义的行为将包括在finally块运行之前破坏未处理的exception点。
CLR团队非常了解这些语义引入安全性和正确性问题的事实; 实际上我在我的第一本书中讨论过,这本书早在十年前就已经在我的博客上发行了:
https://blogs.msdn.microsoft.com/ericlippert/2004/09/01/finally-does-not-mean-immediately/
即使CLR团队想要,现在“修复”语义也是一个突破性的改变。
该特性一直存在于CIL和VB.NET中,攻击者通过filter来控制代码的实现语言,因此向C#引入特性不会增加新的攻击面。
事实上,引入安全问题的这个特性几十年来一直处于“疯狂”状态,据我所知,从来没有出现严重的安全问题,这certificate这对攻击者来说不是一个非常有效的途径。
那么为什么在VB.NET的第一个版本的function,并花了十年,使其成为C#? 那么“为什么不”这样的问题很难回答,但在这种情况下,我可以很容易地总结出来:(1)我们脑海中有很多其他的东西,(2)安德斯认为这个特征没有吸引力。 (而且我对此也不感兴趣。)这已经使它在多年的优先级列表的底部。
那么它是如何使得C#6中的优先级列表足够高呢? 许多人要求这个function,总是赞成这样做。 VB已经有了它,而C#和VB团队在合理的成本下可能会有平价,所以这也是重点。 但是最大的转折点是: Roslyn项目本身有一个场景,那里的exceptionfilter真的很有用。 (我不记得那是什么东西;如果你想find它并且回报,请在源代码中潜水!)
作为一个语言devise者和编译器编写者,你要小心的不要优先考虑使编译器编写者的生活更轻松的特性; 大多数C#用户不是编译器编写者,他们是客户! 但是最终,有一些真实世界的特性是有用的,包括一些令编译器团队本身感到恼火的实例,这些都是平衡的。
但是这不是一个catch语句里面的if语句吗?
不行,因为你的第二种方法在没有的when
不会到达第二个Catch
如果ex.Status== WebExceptionStatus.SendFailure
。 when
第一个Catch
将被跳过。
所以,没有when
处理这个Status
的唯一方法就是把逻辑放在一个方面:
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
将确保在这里仅处理status=Timeout
或SendFailure
,类似于when
方法。 所有其他人将不会被处理,exception将被传播。 请注意,它不会被最后一次Catch
,所以时间还是有区别的。 这显示了when
的优点之一。
这不是一个catch语句里面的if语句吗?
不。它更多地作为“鉴别者”,为了投掷例外系统的利益。
还记得例外抛出两次吗?
第一个“抛出”(这是“工作室继续前进”的那些“第一次机会”exception)告诉运行时find可以处理这种exceptiontypes的最近的exception处理程序,并收集“这里和那里”。
第二个“throw”展开调用堆栈,依次执行这些“finally”块中的每一个,然后将执行引擎传递到定位的exception处理代码的入口点。
以前,我们只能区分不同types的例外。 这个装饰器给了我们更好的纹理控制,只捕捉一个特定types的exception, 恰好处于我们可以做些什么的状态 。
例如(空气稀薄)你可能想要处理一个“数据库exception”,指出连接断开,当发生这种情况时,尝试自动重新连接。
很多数据库操作都会抛出一个“数据库exception”,但是您只是基于Exception对象的属性对它们中的某个“子types” 感兴趣 ,所有这些都可用于exception抛出系统。
catch块中的“if”语句将达到相同的最终结果,但在运行时将会“花费”更多。 因为这个块会捕获任何和所有的 “数据库exception”,即使它只能对其中很小的一部分进行操作,它也会被调用。 这也意味着你必须重新抛出所有你不能做任何有用的例外,这只是重复整个两遍,处理程序发现,最后收获,抛出exception的法拉戈再次。
比喻:一个[非常奇怪]的收费桥梁。
默认情况下,你必须“抓住”每辆车,以便付费。 如果说,由城市雇员驾驶的汽车免费(我确实说这很奇怪),那么你只需要停止其他人驾驶的汽车。
你可以停止每一辆车,并问:
catch( Car car ) { if ( car.needsToPayToll() ) takePayment( car ); }
或者,如果你有某种方式“盘问”汽车的接近,那么你可以忽略那些由城市雇员驱动的汽车,例如:
catch( Car car ) when car.needsToPayToll() { takePayment( car ); }
由Tim扩展答案。
C#6.0引入了一个新的特性exceptionfilter和一个新的关键字。
try语句中的“when”关键字与if语句相同吗?
when关键字的工作方式类似于if。 when condition是一个谓词expression式,可以附加到catch块。 如果谓词expression式评估为true,则执行关联的catch块。 否则,catch块将被忽略。
C#6.0例外filter和关键字时,给出了一个很好的解释