新的"异常过滤器"功能提供了哪些好处?

sta*_*low 32 c# exception-handling exception c#-6.0

C#6有一个名为"异常过滤"的新功能

语法是这样的:

catch (Win32Exception exception) when (exception.NativeErrorCode == 0x00042)  
{  
    //Do something here 
}  
Run Code Online (Sandbox Code Playgroud)

我不禁想知道当前的方法有什么好处:

catch (Win32Exception exception)   
{
     if (exception.NativeErrorCode == 0x00042)
     {
          //Do something here 
     }
}
Run Code Online (Sandbox Code Playgroud)

在大括号之前进行过滤是一件大事吗?也许与性能或安全性有关?

i3a*_*non 33

C#6.0中的异常过滤器功能提供了各种好处.这是对一些的解释(按我认为的重要性排序)

  • 功能奇偶校验 - 异常过滤器已经在IL级别和其他.Net语言(VB.Net和F#)[1]中实现,并且作为构建C#和VB.Net(项目"Roslyn")的新编译器的一部分,许多功能以一种语言存在而另一种语言缺乏[2].

  • 崩溃转储 - 异常过滤器不会修改堆栈.这意味着如果它被转储(在崩溃转储中),你将能够知道最初抛出异常的位置,而不仅仅是它被重新抛出的位置(这与实际问题无关)[3]

  • 调试 -当异常进入catch块,重新抛出使用throw;,并且堆栈不处理其他地方(和例外设置被设置为打破时异常是用户未处理)调试器将打破throw;,而不是其中的例外是最初抛出(即在下面的例子中,它会破坏throw;而不是throw new FileNotFoundException();)

  • 多个catch - 没有异常过滤器,您必须捕获异常,检查条件以及是否未满足throw;异常.catch即使异常满足最终导致单个大块的所有条件,重新抛出的异常也不会考虑任何其他catch

    try
    {
        throw new FileNotFoundException();
    }
    catch (FileNotFoundException e)
    {
        if (!e.FileName.Contains("Hamster"))
        {
            throw;
        }
        // Handle
    }
    catch (Exception e)
    {
        // Unreachable code
    }
    
    Run Code Online (Sandbox Code Playgroud)
  • 可读性 - 虽然您可以使用catch具有许多条件的"catch all" 块并且throw;当它们不被满足时(同时对堆栈进行修改),但是使用单独且不同的catch块更清楚,其中每个块以适当的方式处理特定问题:

    try
    {
    }
    catch (Win32Exception exception) when (exception.NativeErrorCode == 0x00042)  
    { 
    }  
    catch (Win32Exception exception) when (exception.NativeErrorCode == 0x00011)  
    {  
    }
    catch (IOException)
    {  
    }
    
    Run Code Online (Sandbox Code Playgroud)
  • "滥用" - 您可以使用异常过滤器来检查异常而不处理它.虽然这不是主要的好处,但它是一个很好的副作用.您可以使用错误返回的日志记录方法和空catch块:

    private static bool LogException(Exception exception)
    {
        Console.WriteLine(exception.Message);
        return false;
    }
    
    try
    {
    }
    catch (ArgumentException exception) when (LogException(exception))  
    {
        // Unreachable code.
    }
    
    Run Code Online (Sandbox Code Playgroud)

总之,大多数C#6.0功能都是小改进和语法糖,虽然异常过滤器不是一个非常大的功能,它确实提供了以前无法实现的功能.


  1. AC#6.0语言预览

    C#6.0中的另一个例外改进 - 支持异常过滤器 - 使语言与其他.NET语言(即Visual Basic .NET和F#)保持同步

  2. C#6和VB 14中的语言功能

  3. C#6中的新功能

    异常过滤器比捕获和重新抛出更可取,因为它们可以保持堆栈不受破坏.如果稍后的异常导致堆栈被转储,您可以看到它最初来自哪里,而不仅仅是它重新抛出的最后一个位置.


Tho*_*que 12

ErenErsönmez已经提到了最重要的好处.

我只想补充说,当捕获异常时,它具有展开堆栈的效果.这意味着如果你重新抛出异常throw;并且它没有被捕获到堆栈的更高位置,调试器将突出显示throw,而不是最初抛出异常的位置,因此当异常时你无法看到变量的状态被扔了.

另一方面,异常过滤器不会展开堆栈,因此如果未捕获异常,调试器将显示最初抛出异常的位置.

所以,如果你有这个:

try
{
    DoSomethingThatThrows();
}
catch (Win32Exception exception)   
{
     if (exception.NativeErrorCode == 0x00042)
     {
          //Do something here 
     }
     else
     {
         throw;
     }
}
Run Code Online (Sandbox Code Playgroud)

throwNativeErrorCode不是0x42 时,调试器将停止.

但如果你有这个:

try
{
    DoSomethingThatThrows();
}
catch (Win32Exception exception) if (exception.NativeErrorCode == 0x00042)
{         
     //Do something here 
}
Run Code Online (Sandbox Code Playgroud)

调试器将停止DoSomethingThatThrows(或在它调用的方法中),让你更容易看到导致错误的原因(同样,当NativeErrorCode不是0x42时)


Gig*_*igi 7

从官方C#功能描述(PDF下载链接,如VS2015预览版):

异常过滤器比捕获和重新抛出更可取,因为它们可以保持堆栈不受破坏.如果稍后的异常导致堆栈被转储,您可以看到它最初来自哪里,而不仅仅是它重新抛出的最后一个位置.

使用异常过滤器进行副作用也是一种常见且被接受的"滥用"形式; 例如伐木.他们可以在不拦截其路线的情况下检查"飞过"的异常.在这些情况下,过滤器通常会调用一个错误返回的辅助函数来执行副作用:

private static bool Log(Exception e) { /* log it */ ; return false; }
…
try { … } catch (Exception e) if (Log(e)) {}
Run Code Online (Sandbox Code Playgroud)


Ere*_*mez 6

它允许检查条件而不捕获异常,这意味着如果不满足条件,则考虑下一个catch块.如果你抓住并重新投掷,则不会考虑下一个拦截区块.


lep*_*pie 5

真正的好处IMO:

try
{
}
catch (SomeException ex) if (ex.Something)
{
}
catch (Exception ex)
{
  // SomeException with !ex.Something is caught here
  // not the same as calling `throw` for !ex.Something 
  // without filters in previous handler
  // less code duplication
}
Run Code Online (Sandbox Code Playgroud)