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

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

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

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

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


当前回答

异常过滤器现在在c#6+中可用。你可以的

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

在C#7.0+中,您也可以将其与模式匹配相结合

try
{
   await Task.WaitAll(tasks);
}
catch (Exception ex) when( ex is AggregateException ae &&
                           ae.InnerExceptions.Count > tasks.Count/2)
{
   //More than half of the tasks failed maybe..? 
}

其他回答

编辑:我确实同意其他人的观点,即从C#6.0开始,异常过滤器现在是一个非常好的方法:在(ex是…||ex是…)时捕获(exception ex)

除了我仍然有点讨厌一行长的布局,我个人会像下面这样布局代码。我认为这既实用又美观,因为我相信它能提高理解力。有些人可能不同意:

catch (Exception ex) when (
    ex is ...
    || ex is ...
    || ex is ...
)

原件:

我知道我来这里参加派对有点晚了,但圣烟。。。

直接切入主题,这种类型重复了前面的答案,但如果您真的想对几个异常类型执行一个通用操作,并在一个方法的范围内保持整个操作的整洁,为什么不使用lambda/closure/inline函数来执行以下操作呢?我的意思是,很有可能你最终会意识到你只是想让闭包成为一个单独的方法,你可以在所有地方使用它。但是,这样做将非常容易,而实际上不需要从结构上改变代码的其余部分。正确的

private void TestMethod ()
{
    Action<Exception> errorHandler = ( ex ) => {
        // write to a log, whatever...
    };

    try
    {
        // try some stuff
    }
    catch ( FormatException  ex ) { errorHandler ( ex ); }
    catch ( OverflowException ex ) { errorHandler ( ex ); }
    catch ( ArgumentNullException ex ) { errorHandler ( ex ); }
}

我忍不住想知道(警告:前面有点讽刺/讽刺),到底为什么要这么做,基本上只是取代以下内容:

try
{
    // try some stuff
}
catch( FormatException ex ){}
catch( OverflowException ex ){}
catch( ArgumentNullException ex ){}

……我是说,下一个代码的味道有一些疯狂的变化,只是假装你节省了几个按键。

// sorta sucks, let's be honest...
try
{
    // try some stuff
}
catch( Exception ex )
{
    if (ex is FormatException ||
        ex is OverflowException ||
        ex is ArgumentNullException)
    {
        // write to a log, whatever...
        return;
    }
    throw;
}

因为它当然不会自动变得更可读。

当然,我留下了/*写入日志的三个相同实例,不管怎样…*/回来在第一个例子中。

但这是我的观点。你们都听说过函数/方法,对吧?认真地编写一个通用的ErrorHandler函数,然后从每个catch块调用它。

若你们问我,第二个例子(带有If和is关键字)的可读性明显降低,同时在项目的维护阶段也更容易出错。

对于任何可能对编程相对陌生的人来说,维护阶段将占项目整个生命周期的98.7%或更多,而做维护的可怜的笨蛋几乎肯定会是其他人。而且他们很有可能会在工作中花费50%的时间咒骂你的名字。

当然,FxCop会对你咆哮,所以你还必须在代码中添加一个属性,该属性与正在运行的程序完全相关,它只是告诉FxCop忽略一个问题,在99.9%的情况下,它在标记中是完全正确的。抱歉,我可能搞错了,但“忽略”属性最终不是真正编译到了你的应用程序中吗?

将整个if测试放在一行上会让它更易读吗?我不这么认为。我的意思是,很久以前,我确实有另一位程序员激烈地争辩说,在一行代码中添加更多代码会让它“运行得更快”。但当然,他是个彻头彻尾的疯子。试图向他解释(用一副严肃的面孔——这很有挑战性),解释程序或编译器如何将那条长的行分割成每行一条指令的离散语句——基本上与如果他继续下去,只是让代码可读,而不是试图让编译器变得聪明的话,结果是一样的——对他没有任何影响。但我跑题了。

当您在一两个月后再添加三种异常类型时,这会降低多少可读性?(答:它的可读性大大降低)。

其中一个要点是,格式化我们每天都在查看的文本源代码的主要目的是让其他人真正了解代码运行时的实际情况。因为编译器会将源代码转换成完全不同的东西,并且不会对代码格式样式不太在意。所以一条线上的一切也很糟糕。

只是说。。。

// super sucks...
catch( Exception ex )
{
    if ( ex is FormatException || ex is OverflowException || ex is ArgumentNullException )
    {
        // write to a log, whatever...
        return;
    }
    throw;
}

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

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

// 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和调试器未捕获的异常。

异常过滤器现在在c#6+中可用。你可以的

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

在C#7.0+中,您也可以将其与模式匹配相结合

try
{
   await Task.WaitAll(tasks);
}
catch (Exception ex) when( ex is AggregateException ae &&
                           ae.InnerExceptions.Count > tasks.Count/2)
{
   //More than half of the tasks failed maybe..? 
}

约瑟夫·戴格尔的答案是一个很好的解决方案,但我发现下面的结构更整洁,更不容易出错。

catch(Exception ex)
{   
    if (!(ex is SomeException || ex is OtherException)) throw;

    // Handle exception
}

反转表达式有几个优点:

return语句不是必需的代码未嵌套不存在忘记“抛出”或“返回”语句的风险,这些语句在Joseph的解决方案中与表达式分离。

它甚至可以压缩成一行(虽然不是很漂亮)

catch(Exception ex) { if (!(ex is SomeException || ex is OtherException)) throw;

    // Handle exception
}

编辑:C#6.0中的异常过滤将使语法更加简洁,并比任何当前解决方案都有许多其他好处。(最值得注意的是,未损坏堆栈)

下面是使用C#6.0语法时相同问题的外观:

catch(Exception ex) when (ex is SomeException || ex is OtherException)
{
    // Handle exception
}

这里值得一提。您可以响应多个组合(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
}