捕捉exception与“赶上,当”

我在C#中遇到了这个新特性,它允许在满足特定条件时执行catch处理程序。

int i = 0; try { throw new ArgumentNullException(nameof(i)); } catch (ArgumentNullException e) when (i == 1) { Console.WriteLine("Caught Argument Null Exception"); } 

我正试图了解何时可能会有用。

一种情况可能是这样的:

 try { DatabaseUpdate() } catch (SQLException e) when (driver == "MySQL") { //MySQL specific error handling and wrapping up the exception } catch (SQLException e) when (driver == "Oracle") { //Oracle specific error handling and wrapping up of exception } .. 

但这也是我可以在同一个处理程序中执行的操作,并根据驱动程序的types委托给不同的方法。 这是否使代码更容易理解? 可以说没有。

我能想到的另一个场景是这样的:

 try { SomeOperation(); } catch(SomeException e) when (Condition == true) { //some specific error handling that this layer can handle } catch (Exception e) //catchall { throw; } 

再次,这是我可以做的事情:

 try { SomeOperation(); } catch(SomeException e) { if (condition == true) { //some specific error handling that this layer can handle } else throw; } 

是否使用'catch,when'特性使exception处理更快,因为处理程序被跳过,并且堆栈展开可能比处理处理程序中的特定用例更早发生? 有什么具体的用例可以更好地适应这个特性,然后人们可以采用这种用例作为一种好的做法?

捕获块已经允许您筛选exception的types

 catch (SomeSpecificExceptionType e) {...} 

when子句允许您将此filter扩展为通用expression式。

因此, 如果exception的types不足以确定是否应该在此处理exception,则使用when子句。


常见的用例是exceptiontypes,它们实际上是多种不同types的错误的包装

这是我实际使用过的一个案例(在VB中已经有了这个function很长一段时间了):

 try { SomeLegacyComOperation(); } catch (COMException e) when (e.ErrorCode == 0x1234) { // Handle the *specific* error I was expecting. } 

SqlException也是如此,它也有一个ErrorCode属性。 替代scheme会是这样的:

 try { SomeLegacyComOperation(); } catch (COMException e) { if (e.ErrorCode == 0x1234) { // Handle error } else { throw; } } 

这可以说是不太优雅, 稍微打破了堆栈跟踪 。

另外,你可以在同一个try-catch-block中两次提到相同types的exception:

 try { SomeLegacyComOperation(); } catch (COMException e) when (e.ErrorCode == 0x1234) { ... } catch (COMException e) when (e.ErrorCode == 0x5678) { ... } 

如果没有条件,这是不可能的。

来自Roslyn的wiki (重点是我的):

exceptionfilter优于捕捉和重新抛出,因为它们不会受到伤害 。 如果这个exception后来导致堆栈被抛弃,你可以看到它最初来自哪里,而不是最后一个被重新抛出的地方。

对于副作用使用exceptionfilter也是一种常见和可接受的“滥用”forms; 如日志。 他们可以检查一个例外“飞”,而不会拦截其过程 。 在这些情况下,filter通常是对执行副作用的错误返回帮助函数的调用:

 private static bool Log(Exception e) { /* log it */ ; return false; } … try { … } catch (Exception e) when (Log(e)) { } 

第一点值得展示。

 static class Program { static void Main(string[] args) { A(1); } private static void A(int i) { try { B(i + 1); } catch (Exception ex) { if (ex.Message != "!") Console.WriteLine(ex); else throw; } } private static void B(int i) { throw new Exception("!"); } } 

如果我们在WinDbg中运行这个函数,直到遇到exception,并使用!clrstack -i -a打印堆栈,我们将看到A

 003eef10 00a7050d [DEFAULT] Void App.Program.A(I4) PARAMETERS: + int i = 1 LOCALS: + System.Exception ex @ 0x23e3178 + (Error 0x80004005 retrieving local variable 'local_1') 

但是,如果我们改变程序使用的when

 catch (Exception ex) when (ex.Message != "!") { Console.WriteLine(ex); } 

我们将看到堆栈还包含B的框架:

 001af2b4 01fb05aa [DEFAULT] Void App.Program.B(I4) PARAMETERS: + int i = 2 LOCALS: (none) 001af2c8 01fb04c1 [DEFAULT] Void App.Program.A(I4) PARAMETERS: + int i = 1 LOCALS: + System.Exception ex @ 0x2213178 + (Error 0x80004005 retrieving local variable 'local_1') 

这些信息在debugging崩溃转储时非常有用。

当引发exception时,exception处理的第一遍标识了展开堆栈之前exception将被捕获的位置; 如果/当“catch”位置被识别时,所有的“finally”块将被运行(注意,如果exception转义了“finally”块,则可以放弃对前面的exception的处理)。 一旦发生这种情况,代码将在“catch”处继续执行。

如果在作为“何时”的一部分评估的函数中存在断点,则在任何堆栈解绕发生之前,该断点将暂停执行; 相反,一个“catch”的断点只会在finally所有处理程序运行后才会暂停执行。

最后,如果foo第23行和第27行调用bar ,并且第23行的调用引发了一个在foo捕获并在第57行重新生成的exception,则堆栈跟踪将build议在从第57行调用bar发生exception重新抛出],摧毁有关23号线或27号线呼叫是否发生exception的任何信息。 首先避免捕捉exception的when避免了这种干扰。

顺便说一句,在C#和VB.NET中令人讨厌的尴尬的有用模式是在when子句中使用一个函数调用来设置一个可以在finally子句中使用的variables,以确定函数是否正常完成,以处理一个职能没有希望“解决”发生的任何exception,但必须采取行动的基础上。 例如,如果在应该返回封装资源的对象的工厂方法中引发exception,则需要释放获取的任何资源,但底层exception应该渗透到调用方。 在语义上(尽pipe不是语法上)处理这个问题最简单的方法是通过finally块检查是否发生exception,如果是,则释放代表不再返回的对象获取的所有资源。 由于清理代码没有希望解决导致exception的任何条件,所以它不应该catch它,而只需要知道发生了什么。 调用一个函数,如:

 bool CopySecondArgumentToFirstAndReturnFalse<T>(ref T first, T second) { first = second; return false; } 

在一个when子句中,工厂函数就可以知道发生了什么事情。