我在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
}
..
但这又是我可以在同一个处理程序中执行的操作,并根据驱动程序的类型委托给不同的方法。这是否使代码更容易理解?可以说没有。
我能想到的另一种情况是:
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,'功能使异常处理更快,因为处理程序被如此跳过,并且与处理处理程序中的特定用例相比,堆栈展开可以更早发生?是否有更适合此功能的特定用例,人们可以将其作为一种良好做法?
答案 0 :(得分:96)
Catch块已经允许您过滤异常的类型:
catch (SomeSpecificExceptionType e) {...}
when
子句允许您将此过滤器扩展为通用表达式。
因此,您使用when
子句来处理异常的类型不足以确定是否应在此处理异常的情况。强>
一个常见的用例是异常类型,它实际上是一个包装器,用于多种不同类型的错误。
这是我实际使用的一个案例(在VB中,已经有这个功能很长一段时间了):
try
{
SomeLegacyComOperation();
}
catch (COMException e) when (e.ErrorCode == 0x1234)
{
// Handle the *specific* error I was expecting.
}
SqlException
也是如此,它也有ErrorCode
属性。替代方案将是这样的:
try
{
SomeLegacyComOperation();
}
catch (COMException e)
{
if (e.ErrorCode == 0x1234)
{
// Handle error
}
else
{
throw;
}
}
可以说不那么优雅slightly breaks the stack trace。
此外,您可以在同一个try-catch-block中两次提及相同的类型异常:
try
{
SomeLegacyComOperation();
}
catch (COMException e) when (e.ErrorCode == 0x1234)
{
...
}
catch (COMException e) when (e.ErrorCode == 0x5678)
{
...
}
如果没有when
条件,这是不可能的。
答案 1 :(得分:35)
来自罗斯林的wiki(强调我的):
异常过滤器比捕获和重新抛出更可取,因为 他们不受伤害。如果异常后来导致堆栈 要被倾倒,你可以看到它最初的来源,而不是 只是最后一个地方被重新抛出。
使用例外也是一种常见且被接受的“滥用”形式 过滤器的副作用;例如日志记录。他们可以检查异常 “飞过”而不拦截其路线。在那些情况下, 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中运行此命令直到命中异常,并使用!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')
在调试故障转储时,该信息非常有用。
答案 2 :(得分:5)
当抛出异常时,异常处理的第一次传递确定在展开堆栈之前将捕获到异常的位置;如果/当识别出“catch”位置时,运行所有“finally”块(注意,如果异常逃脱“finally”块,则可以放弃对先前异常的处理)。一旦发生这种情况,代码将在“catch”处继续执行。
如果函数中有一个断点作为“when”的一部分进行评估,那么断点将在任何堆栈展开之前暂停执行;相比之下,“catch”处的断点只会在所有finally
个处理程序运行后暂停执行。
最后,如果foo
的第23行和第27行调用bar
,第23行的调用将引发一个异常,该异常在foo
内捕获并在第57行重新抛出,则堆栈trace将表明在从第57行[rethrow的位置]调用bar
时发生异常,从而破坏有关在23行或27行调用中是否发生异常的任何信息。使用when
来避免首先捕获异常可以避免这种干扰。
when
子句中使用函数调用来设置一个可以在finally
子句中使用的变量判断函数是否正常完成,以处理函数无法“解决”发生的任何异常但仍必须根据它执行操作的情况。例如,如果在应该返回封装资源的对象的工厂方法中抛出异常,则需要释放所获取的任何资源,但是底层异常应该渗透到调用者。处理语义(尽管不是语法上)的最干净方法是让finally
块检查是否发生了异常,如果是,则释放代表不再返回的对象获取的所有资源。由于清理代码无法解决导致异常的任何条件,因此它确实不应该catch
它,而只需要知道发生了什么。调用类似的函数:
bool CopySecondArgumentToFirstAndReturnFalse<T>(ref T first, T second)
{
first = second;
return false;
}
在when
子句中的将使工厂函数可以知道 发生了什么事。