不鼓励只捕获System.Exception。相反,只应捕获“已知”异常。

现在,这有时会导致不必要的重复代码,例如:

try
{
    WebId = new Guid(queryString["web"]);
}
catch (FormatException)
{
    WebId = Guid.Empty;
}
catch (OverflowException)
{
    WebId = Guid.Empty;
}

我想知道:是否有一种方法可以捕获两个异常,并且只调用WebId=Guid.Empty调用一次?

给定的示例相当简单,因为它只是一个GUID。但是想象一下,在代码中,您多次修改一个对象,如果其中一个操作预期失败,您希望“重置”该对象。然而,如果有意外的异常,我仍然想把它推得更高。


当前回答

如果您不想在catch范围内使用If语句,在C#6.0中,您可以使用CLR在预览版本中已经支持但仅存在于VB.NET/MSIL中的异常过滤器语法:

try
{
    WebId = new Guid(queryString["web"]);
}
catch (Exception exception) when (exception is FormatException || ex is OverflowException)
{
    WebId = Guid.Empty;
}

只有当异常为InvalidDataException或ArgumentNullException时,此代码才会捕获该异常。

实际上,你可以在when子句中放入基本上任何条件:

static int a = 8;

...

catch (Exception exception) when (exception is InvalidDataException && a == 8)
{
    Console.WriteLine("Catch");
}

请注意,与catch范围内的if语句不同,异常过滤器不能抛出异常,当抛出异常时,或者当条件不为真时,将计算下一个catch条件:

static int a = 7;

static int b = 0;

...

try
{
    throw new InvalidDataException();
}
catch (Exception exception) when (exception is InvalidDataException && a / b == 2)
{
    Console.WriteLine("Catch");
}
catch (Exception exception) when (exception is InvalidDataException || exception is ArgumentException)
{
    Console.WriteLine("General catch");
}

输出:常规捕获。

当存在多个真正的异常筛选器时,将接受第一个:

static int a = 8;

static int b = 4;

...

try
{
    throw new InvalidDataException();
}
catch (Exception exception) when (exception is InvalidDataException && a / b == 2)
{
    Console.WriteLine("Catch");
}
catch (Exception exception) when (exception is InvalidDataException || exception is ArgumentException)
{
    Console.WriteLine("General catch");
}

输出:捕获。

正如您在MSIL中看到的,代码不是转换为if语句,而是转换为筛选器,并且异常不能从标记为筛选器1和筛选器2的区域中抛出,但是抛出异常的筛选器将失败,在endfilter命令之前推送到堆栈的最后一个比较值也将决定过滤器的成功/失败(Catch 1 XOR Catch 2将相应地执行):

此外,特别是Guid具有Guid.TryParse方法。

其他回答

想在这条已经很长的线索上加上我的简短回答。还没有提到的是catch语句的优先顺序,更具体地说,您需要了解您试图捕获的每种类型的异常的范围。

例如,如果您使用“catch-all”异常作为异常,它将优先于所有其他catch语句,您显然会遇到编译器错误。但是,如果您颠倒了catch语句的顺序(我认为这有点反模式),您可以将catch-allException类型放在底部,这将捕获任何在尝试中不适合更高级别的异常。。捕捉块:

            try
            {
                // do some work here
            }
            catch (WebException ex)
            {
                // catch a web excpetion
            }
            catch (ArgumentException ex)
            {
                // do some stuff
            }
            catch (Exception ex)
            {
                // you should really surface your errors but this is for example only
                throw new Exception("An error occurred: " + ex.Message);
            }

我强烈建议大家阅读此MSDN文档:

异常层次结构

这是马特的答案的变体(我觉得这有点干净)。。。使用一种方法:

public void TryCatch(...)
{
    try
    {
       // something
       return;
    }
    catch (FormatException) {}
    catch (OverflowException) {}

    WebId = Guid.Empty;
}

将引发任何其他异常,代码WebId=Guid.Empty;不会被击中。如果您不希望其他异常使您的程序崩溃,只需在其他两个捕获之后添加这个:

...
catch (Exception)
{
     // something, if anything
     return; // only need this if you follow the example I gave and put it all in a method
}

C#9的更新

使用C#9中的新模式匹配增强功能,可以缩短异常过滤器中的表达式。现在,捕获多个异常很简单,如下所示:

try
{
    WebId = new Guid(queryString["web"]);
}
catch (Exception e) when (e is FormatException or OverflowException)
{
    WebId = Guid.Empty;
}

正如其他人所指出的,您可以在catch块中使用if语句来确定发生了什么。C#6支持异常过滤器,因此以下操作将起作用:

try { … }
catch (Exception e) when (MyFilter(e))
{
    …
}

MyFilter方法可能看起来像这样:

private bool MyFilter(Exception e)
{
  return e is ArgumentNullException || e is FormatException;
}

或者,这可以全部内联完成(when语句的右侧必须是布尔表达式)。

try { … }
catch (Exception e) when (e is ArgumentNullException || e is FormatException)
{
    …
}

这与在catch块中使用if语句不同,使用异常过滤器不会展开堆栈。

您可以下载Visual Studio 2015来查看。

如果要继续使用Visual Studio 2013,可以安装以下nuget包:

安装程序包Microsoft.Net.Compilers

在撰写本文时,这将包括对C#6的支持。

引用此包将导致使用中包含的C#和Visual Basic编译器的特定版本与任何系统安装版本不同。

为了完整起见,自.NET 4.0以来,代码可以重写为:

Guid.TryParse(queryString["web"], out WebId);

TryParse从不抛出异常,如果格式错误,则返回false,将WebId设置为Guid.Empty。


由于C#7,您可以避免在单独的行中引入变量:

Guid.TryParse(queryString["web"], out Guid webId);

您还可以创建用于解析返回元组的方法,这些方法在.NET Framework 4.6版之前尚不可用:

(bool success, Guid result) TryParseGuid(string input) =>
    (Guid.TryParse(input, out Guid result), result);

并像这样使用它们:

WebId = TryParseGuid(queryString["web"]).result;
// or
var tuple = TryParseGuid(queryString["web"]);
WebId = tuple.success ? tuple.result : DefaultWebId;

当在C#12中实现out参数的解构时,这个无用答案的下一个无用更新就来了