用"捕获异常,当"

时间:2016-07-21 07:31:51

标签: 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");
}

我想知道什么时候这可能有用。

一种情况可能是这样的:

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,'功能使异常处理更快,因为处理程序被如此跳过,并且与处理处理程序中的特定用例相比,堆栈展开可以更早发生?是否有更适合此功能的特定用例,人们可以将其作为一种良好做法?

3 个答案:

答案 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来避免首先捕获异常可以避免这种干扰。

BTW,一个在C#和VB.NET中令人烦恼的有用模式是在when子句中使用函数调用来设置一个可以在finally子句中使用的变量判断函数是否正常完成,以处理函数无法“解决”发生的任何异常但仍必须根据它执行操作的情况。例如,如果在应该返回封装资源的对象的工厂方法中抛出异常,则需要释放所获取的任何资源,但是底层异常应该渗透到调用者。处理语义(尽管不是语法上)的最干净方法是让finally块检查是否发生了异常,如果是,则释放代表不再返回的对象获取的所有资源。由于清理代码无法解决导致异常的任何条件,因此它确实不应该catch它,而只需要知道发生了什么。调用类似的函数:

bool CopySecondArgumentToFirstAndReturnFalse<T>(ref T first, T second)
{
  first = second;
  return false;
}
when子句中的

将使工厂函数可以知道 发生了什么事。