MS *_*nth 85 c# exception-handling
我在C#中遇到了这个新功能,它允许在满足特定条件时执行catch处理程序.
int i = 0;
try
{
throw new ArgumentNullException(nameof(i));
}
catch (ArgumentNullException e)
when (i == 1)
{
Console.WriteLine("Caught Argument Null Exception");
}
Run Code Online (Sandbox Code Playgroud)
我想知道什么时候这可能有用.
一种情况可能是这样的:
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
}
..
Run Code Online (Sandbox Code Playgroud)
但这又是我可以在同一个处理程序中执行的操作,并根据驱动程序的类型委托给不同的方法.这是否使代码更容易理解?可以说没有.
我能想到的另一个场景是:
try
{
SomeOperation();
}
catch(SomeException e)
when (Condition == true)
{
//some specific error handling that this layer can handle
}
catch (Exception e) //catchall
{
throw;
}
Run Code Online (Sandbox Code Playgroud)
这也是我能做的事情:
try
{
SomeOperation();
}
catch(SomeException e)
{
if (condition == true)
{
//some specific error handling that this layer can handle
}
else
throw;
}
Run Code Online (Sandbox Code Playgroud)
是否使用'catch,when'功能可以更快地处理异常,因为处理程序被如此跳过,并且与处理处理程序中的特定用例相比,堆栈展开可以更早发生?是否有更适合此功能的特定用例,人们可以将其作为一种良好做法?
Hei*_*nzi 106
Catch块已经允许您过滤异常的类型:
catch (SomeSpecificExceptionType e) {...}
Run Code Online (Sandbox Code Playgroud)
该when子句允许您将此过滤器扩展为通用表达式.
因此,对于异常类型不够明确以确定是否应在此处理异常的情况,请使用该when子句.
常见的用例是异常类型,它实际上是多种不同类型错误的包装器.
这是我实际使用的一个案例(在VB中,已经有这个功能很长一段时间了):
try
{
SomeLegacyComOperation();
}
catch (COMException e) when (e.ErrorCode == 0x1234)
{
// Handle the *specific* error I was expecting.
}
Run Code Online (Sandbox Code Playgroud)
同样的SqlException,也有一个ErrorCode属性.替代方案将是这样的:
try
{
SomeLegacyComOperation();
}
catch (COMException e)
{
if (e.ErrorCode == 0x1234)
{
// Handle error
}
else
{
throw;
}
}
Run Code Online (Sandbox Code Playgroud)
可以说它不那么优雅,并略微打破了堆栈的痕迹.
此外,您可以在同一个try-catch-block中两次提到相同类型的异常:
try
{
SomeLegacyComOperation();
}
catch (COMException e) when (e.ErrorCode == 0x1234)
{
...
}
catch (COMException e) when (e.ErrorCode == 0x5678)
{
...
}
Run Code Online (Sandbox Code Playgroud)
没有when条件就不可能.
Eli*_*bel 37
来自Roslyn的wiki(强调我的):
异常过滤器比捕获和重新抛出更可取,因为它们可以保持堆栈不受破坏.如果稍后的异常导致堆栈被转储,您可以看到它最初来自哪里,而不仅仅是它重新抛出的最后一个位置.
使用异常过滤器进行副作用也是一种常见且被接受的"滥用"形式; 例如伐木.他们可以在不拦截其路线的情况下检查"飞过"的异常.在这些情况下,过滤器通常会调用一个错误返回的辅助函数来执行副作用:
Run Code Online (Sandbox Code Playgroud)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("!");
}
}
Run Code Online (Sandbox Code Playgroud)
如果我们在WinDbg中运行它直到命中异常,并使用!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')
Run Code Online (Sandbox Code Playgroud)
但是,如果我们更改要使用的程序when:
catch (Exception ex) when (ex.Message != "!")
{
Console.WriteLine(ex);
}
Run Code Online (Sandbox Code Playgroud)
我们将看到堆栈还包含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')
Run Code Online (Sandbox Code Playgroud)
调试崩溃转储时,该信息非常有用.
抛出异常时,异常处理的第一次传递标识在展开堆栈之前将捕获异常的位置; 如果/当识别出"catch"位置时,运行所有"finally"块(注意,如果异常逃脱"finally"块,则可以放弃对先前异常的处理).一旦发生这种情况,代码将在"catch"处恢复执行.
如果在作为"when"的一部分计算的函数中存在断点,则该断点将在任何堆栈展开之前暂停执行; 相比之下,"catch"处的断点只会在所有finally处理程序运行后暂停执行.
最后,如果foo调用的第23行和第27行bar以及第23行的调用抛出了一个异常,它被捕获foo并在第57 bar行重新抛出,那么堆栈跟踪将表明在从第57行调用时发生了异常[重新抛出的位置] ,销毁有关在23行或27行调用中是否发生异常的任何信息.使用when以避免在第一时间捕获异常避免这种干扰.
BTW,一个在C#和VB.NET中令人烦恼的有用模式是在when子句中使用函数调用来设置一个变量,该变量可以在finally子句中用来确定函数是否正常完成,以处理函数的情况没有希望"解决"任何发生的例外,但必须采取基于它的行动.例如,如果在应该返回封装资源的对象的工厂方法中抛出异常,则需要释放所获取的任何资源,但是底层异常应该渗透到调用者.处理语义(尽管不是语法上)的最干净方法是使用finally块检查是否发生了异常,如果是,则释放代表不再返回的对象获取的所有资源.由于清理代码没有希望解决导致异常的任何条件,它实际上不catch应该,但只需要知道发生了什么.调用如下函数:
bool CopySecondArgumentToFirstAndReturnFalse<T>(ref T first, T second)
{
first = second;
return false;
}
Run Code Online (Sandbox Code Playgroud)
在一个when条款中,工厂功能可以知道发生了什么事.