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

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

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

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

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


当前回答

不幸的是,在C#中没有,因为您需要一个异常过滤器来实现它,而且C#没有公开MSIL的这一特性。VB.NET确实具有此功能,例如。

Catch ex As Exception When TypeOf ex Is FormatException OrElse TypeOf ex Is OverflowException

您可以做的是使用匿名函数封装错误代码,然后在这些特定的catch块中调用它:

Action onError = () => WebId = Guid.Empty;
try
{
    // something
}
catch (FormatException)
{
    onError();
}
catch (OverflowException)
{
    onError();
}

其他回答

因为我觉得这些答案只是触及了表面,所以我试图更深入地挖掘。

所以我们真正想做的是不编译的东西,比如:

// Won't compile... damn
public static void Main()
{
    try
    {
        throw new ArgumentOutOfRangeException();
    }
    catch (ArgumentOutOfRangeException)
    catch (IndexOutOfRangeException) 
    {
        // ... handle
    }

我们之所以要这样做,是因为我们不希望异常处理程序捕获我们稍后在流程中需要的东西。当然,我们可以捕获异常并检查“如果”该怎么做,但老实说,我们并不真的想要这样做。(FxCop,调试器问题,丑陋)

那么,为什么这段代码不能编译呢?我们怎么能以这种方式破解它呢?

如果我们查看代码,我们真正想做的是转发调用。然而,根据MS Partition II,IL异常处理程序块不会像这样工作,这在本例中是有意义的,因为这意味着“异常”对象可以具有不同的类型。

或者用代码编写它,我们要求编译器这样做(这不完全正确,但我想这是最接近的):

// Won't compile... damn
try
{
    throw new ArgumentOutOfRangeException();
}
catch (ArgumentOutOfRangeException e) {
    goto theOtherHandler;
}
catch (IndexOutOfRangeException e) {
theOtherHandler:
    Console.WriteLine("Handle!");
}

这不会编译的原因很明显:“$exception”对象具有什么类型和值(此处存储在变量“e”中)?我们希望编译器处理这一点的方式是注意,这两个异常的公共基类型都是“异常”,将其用于包含这两个例外的变量,然后仅处理捕获的两个例外。在IL中实现这一点的方式是“filter”,它在VB.Net中可用。

为了使它在C#中工作,我们需要一个具有正确“Exception”基类型的临时变量。为了控制代码流,我们可以添加一些分支。这里是:

    Exception ex;
    try
    {
        throw new ArgumentException(); // for demo purposes; won't be caught.
        goto noCatch;
    }
    catch (ArgumentOutOfRangeException e) {
        ex = e;
    }
    catch (IndexOutOfRangeException e) {
        ex = e;
    }

    Console.WriteLine("Handle the exception 'ex' here :-)");
    // throw ex ?

noCatch:
    Console.WriteLine("We're done with the exception handling.");

这样做的明显缺点是我们不能正确地重新投掷,而且——老实说——这是一个非常丑陋的解决方案。通过执行分支消除,可以稍微解决丑陋的问题,这使解决方案稍微好一点:

Exception ex = null;
try
{
    throw new ArgumentException();
}
catch (ArgumentOutOfRangeException e)
{
    ex = e;
}
catch (IndexOutOfRangeException e)
{
    ex = e;
}
if (ex != null)
{
    Console.WriteLine("Handle the exception here :-)");
}

这只剩下“掷骰”了。要使其工作,我们需要能够在“catch”块内执行处理,而使其工作的唯一方法是捕获“Exception”对象。

此时,我们可以添加一个单独的函数,使用重载解析来处理不同类型的异常,或者处理异常。两者都有缺点。首先,以下是使用助手函数的方法:

private static bool Handle(Exception e)
{
    Console.WriteLine("Handle the exception here :-)");
    return true; // false will re-throw;
}

public static void Main()
{
    try
    {
        throw new OutOfMemoryException();
    }
    catch (ArgumentException e)
    {
        if (!Handle(e)) { throw; }
    }
    catch (IndexOutOfRangeException e)
    {
        if (!Handle(e)) { throw; }
    }

    Console.WriteLine("We're done with the exception handling.");

另一种解决方案是捕获Exception对象并相应地处理它。基于以上上下文,最字面意思的翻译如下:

try
{
    throw new ArgumentException();
}
catch (Exception e)
{
    Exception ex = (Exception)(e as ArgumentException) ?? (e as IndexOutOfRangeException);
    if (ex != null)
    {
        Console.WriteLine("Handle the exception here :-)");
        // throw ?
    }
    else 
    {
        throw;
    }
}

因此,总结如下:

如果我们不想重新抛出,我们可以考虑捕获正确的异常,并将它们存储在临时文件中。如果处理程序很简单,并且我们希望重用代码,那么最好的解决方案可能是引入一个助手函数。如果我们想重新抛出,我们别无选择,只能将代码放入“异常”捕获处理程序,这将中断FxCop和调试器未捕获的异常。

我想建议最简短的答案(一种更实用的方式):

        Catch<FormatException, OverflowException>(() =>
            {
                WebId = new Guid(queryString["web"]);
            },
            exception =>
            {
                WebId = Guid.Empty;
            });

为此,您需要创建几个“Catch”方法重载,类似于System.Action:

    [DebuggerNonUserCode]
    public static void Catch<TException1, TException2>(Action tryBlock,
        Action<Exception> catchBlock)
    {
        CatchMany(tryBlock, catchBlock, typeof(TException1), typeof(TException2));
    }

    [DebuggerNonUserCode]
    public static void Catch<TException1, TException2, TException3>(Action tryBlock,
        Action<Exception> catchBlock)
    {
        CatchMany(tryBlock, catchBlock, typeof(TException1), typeof(TException2), typeof(TException3));
    }

你想多少就多少。但是你需要做一次,你可以在所有项目中使用它(或者,如果你创建了一个nuget包,我们也可以使用它)。

以及CatchMany实施:

    [DebuggerNonUserCode]
    public static void CatchMany(Action tryBlock, Action<Exception> catchBlock,
        params Type[] exceptionTypes)
    {
        try
        {
            tryBlock();
        }
        catch (Exception exception)
        {
            if (exceptionTypes.Contains(exception.GetType())) catchBlock(exception);
            else throw;
        }
    }

p.s.为了代码的简单性,我没有进行空检查,请考虑添加参数验证。

第2页如果要从catch返回值,则必须执行相同的catch方法,但在参数中使用return和Func而不是Action。

在C#6中,推荐的方法是使用异常过滤器,下面是一个示例:

 try
 {
      throw new OverflowException();
 }
 catch(Exception e ) when ((e is DivideByZeroException) || (e is OverflowException))
 {
       // this will execute iff e is DividedByZeroEx or OverflowEx
       Console.WriteLine("E");
 }

这里值得一提。您可以响应多个组合(Exceptionerror和Exception.message)。

当我试图在数据网格中转换控件对象时,我遇到了一个用例场景,其中的内容为TextBox、TextBlock或CheckBox。在这种情况下,返回的异常相同,但消息不同。

try
{
 //do something
}
catch (Exception ex) when (ex.Message.Equals("the_error_message1_here"))
{
//do whatever you like
} 
catch (Exception ex) when (ex.Message.Equals("the_error_message2_here"))
{
//do whatever you like
} 

也许试着让你的代码保持简单,比如把公共代码放在一个方法中,就像你在代码的任何其他不在catch子句中的部分所做的那样?

例如。:

try
{
    // ...
}
catch (FormatException)
{
    DoSomething();
}
catch (OverflowException)
{
    DoSomething();
}

// ...

private void DoSomething()
{
    // ...
}

我会怎么做,试图找到简单而美丽的模式