捕捉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
子句中,工厂函数就可以知道发生了什么事情。