题 一次捕获多个异常?


简单地抓住它是不鼓励的 System.Exception。相反,只应捕获“已知”异常。

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

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

我想知道:有没有办法捕获两个例外,只能调用 WebId = Guid.Empty 打电话一次?

给出的例子相当简单,因为它只是一个 GUID。但想象一下你多次修改一个对象的代码,如果其中一个操作以预期的方式失败,你想要“重置” object。但是,如果出现意外异常,我仍然希望将其提高。


1712
2017-09-25 20:56


起源


如果您使用的是.net 4及更高版本,我更喜欢使用aggregateexception msdn.microsoft.com/en-us/library/system.aggregateexception.aspx - Bepenfriends
Bepenfriends-从那以后 的System.Guid 不扔 AggregateException如果你(或某人)可以发布一个答案,展示如何将其包装成AggregateException等,那将是很棒的。 - weir
在使用 AggregateException: 在我自己的代码中抛出AggregateException - DavidRR
“不鼓励简单地捕获System.Exception。” - 如果方法可以抛出32种类型的异常,那么它会做什么?分别为每个人写捕获? - giorgi.m
保持它你的方式。如果您希望每个catch语句只有一行,则将代码移动到错误处理程序。 - rolls


答案:


抓住 System.Exception 并打开类型

catch (Exception ex)            
{                
    if (ex is FormatException || ex is OverflowException)
    {
        WebId = Guid.Empty;
        return;
    }

    throw;
}

1792
2017-09-25 21:01



不幸的是,当您捕获异常时,FxCop(即 - Visual Studio代码分析)不喜欢它。 - Andrew Garrison
我同意不捕获异常,但是,在这种情况下,catch是一个过滤器。您可能有一个更高的层来处理其他异常类型。我会说这是正确的,即使它包含一个catch(异常x)。它不会修改程序流,它只处理某些异常,然后让应用程序的其余部分处理任何其他异常类型。 - lkg
当使用上面的代码时,最新版本的FxCop不会抛出异常。 - Peter
首先不确定OP的代码有什么问题。 #1接受的答案几乎是行数的两倍,可读性差得多。 - João Bragança
@JoãoBragança:虽然这个例子中的答案使用了更多的行,但是试着想象一下你是否正在处理文件IO,而你想要做的就是捕获这些异常并做一些日志消息,但只有那些你希望来自你的文件IO方法。然后,您经常需要处理更多(大约5个或更多)不同类型的异常。在这种情况下,这种方法可以为您节省一些线路。 - Xilconic


编辑: 我同意其他人的观点,从C#6.0开始,异常过滤器现在是一个非常好的方法: catch (Exception ex) when (ex is ... || ex is ... )

除了我仍然讨厌一个长线布局,并将个人像下面那样放置代码。我认为这是功能性的,因为它是审美的,因为我相信它可以提高理解力。有些人可能不同意:

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;
}

因为它肯定不会自动更具可读性。

当然,我离开了三个相同的实例 /* write to a log, whatever... */ return; 在第一个例子中。

但这是我的观点。你们都听说过功能/方法,对吧?认真。写一个共同点 ErrorHandler 函数,以及从每个catch块调用它。

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

维护阶段,对于任何可能相对较新的编程人员来说,将占项目整体生命周期的98.7%或更多,而做维护的穷人也几乎肯定会成为除你以外的其他人。并且他们很有可能将50%的时间花在诅咒你名字的工作上。

当然,FxCop会咆哮你,所以你必须这样做 在代码中添加一个属性,该属性具有与正在运行的程序完全相同的zip,并且只是告诉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;
}

370
2017-10-12 00:24



当我第一次偶然发现这个问题时,我已经完全接受了答案。很酷,我可以抓住所有 Exceptions并检查类型。我认为它清理了代码,但有些东西让我回到这个问题,我实际上已经阅读了问题的其他答案。我嚼了一会儿,但我不得不同意你的看法。它是 更多 可读和可维护使用函数来清理代码而不是捕获所有内容,检查类型与列表,包装代码和抛出的类型。感谢您迟到并提供替代和理智(IMO)选项。 +1。 - erroric
如果要包含a,则使用错误处理功能将不起作用 throw;。你必须在每个catch块中重复那行代码(显然不是世界末日,但值得一提,因为它是需要重复的代码)。 - kad81
@ kad81,这是真的,但是您仍然可以在一个地方编写日志记录和清理代码,并在需要时在一个地方更改它,而不需要捕获基本异常类型然后根据分支进行分支的愚蠢语义异常类型。还有一个 throw(); 每个catch块中的语句都是一个很小的代价,IMO,并且如果有必要,仍然可以让您进行额外的异常类型特定清理。 - Craig
嗨@Reitffunk,请使用 Func<Exception, MyEnumType> 代替 Action<Exception>。那是 Func<T, Result>,与 Result 是回归类型。 - Craig
我在这里完全同意。我也读了第一个答案,思想似乎合情合理。移至所有异常处理程序的通用1。我内心的东西让我内心呕吐......所以我还原了代码。然后遇到了这个美女!这个 需求 成为公认的答案 - Conor Gallagher


正如其他人所指出的,你可以拥有一个 if 在catch块中的语句来确定发生了什么。 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)
{
    …
}

这与使用不同 if 来自内部的声明 catch 阻止,使用异常过滤器 将不会 放松堆栈。

你可以下载 Visual Studio 2015 检查一下。

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

安装包Microsoft.Net.Compilers

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

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


241
2018-04-04 13:59



耐心地等待正式发布的6 ...我希望看到这种情况发生时会发生这种情况。 - RubberDuck
@RubberDuck我正在为C#6中的null传播操作符而死。试图说服我的团队的其他成员,不稳定的语言/编译器的风险是值得的。大量改进带来巨大影响。至于标记为答案,并不重要,只要人们意识到这将是可能的,我很高兴。 - Joe
对?!我将在不久的将来仔细研究我的代码库。 =)我知道检查并不重要,但鉴于接受的答案很快就会过时,我希望OP回来检查这一点,以便给它提供适当的可见性。 - RubberDuck
这就是为什么我还没有授予它@Joe的部分原因。我希望这是可见的。尽管如此,您可能还想添加内联过滤器的示例。 - RubberDuck


不幸的是,不是在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();
}

184
2017-09-25 21:03



有趣的想法和另一个例子,VB.net有时比C#有一些有趣的优势 - Michael Stum♦
@MichaelStum跟 那 一种语法我几乎不称它有趣... 不寒而栗 - MarioDS
c#6中将出现异常过滤器!注意使用过滤器有利于重新抛出的区别 roslyn.codeplex.com/discussions/541301 - Arne Deruwe
@ArneDeruwe感谢您的链接!我刚学会了一个不重新抛出的重要原因: throw e; 破坏堆栈跟踪 和 调用堆栈, throw; 破坏“唯一”的callstack(渲染崩溃转储没用!)A 非常 有充分理由不使用它,如果可以避免的话! - AnorZaken
如果我没有错,你也可以通过VB中的一个问题,就像你可以使用case或者(在c#中切换)语句一样 Try Catch ex As ArgumentException Catch ex As NullReferenceException End Try 但不幸的是,C#不是这样,我们留下了一个辅助方法或者一般地捕捉并确定类型。 - David Carrigan


为了完整起见,从那时起 .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-parameters的解构时,接下来对这个无用的答案进行无用的更新。:)


123
2018-04-13 12:18



准确 - 简洁,你完全绕过了处理异常的性能损失,故意使用异常来控制程序流的坏形式,以及让你的转换逻辑传播的软焦点,在这里稍微有点在那里。 - Craig
我知道你的意思,但当然 Guid.TryParse 永远不会回来 Guid.Empty。如果字符串格式不正确,则设置 result 输出参数为 Guid.Empty, 但它 回报  false。我之所以提到它,是因为我看过代码可以做的事情 Guid.TryParse(s, out guid); if (guid == Guid.Empty) { /* handle invalid s */ },如果,这通常是错误的 s 可能是字符串表示 Guid.Empty。 - hvd
哇你已经回答了这个问题,除了它不符合问题的精神。更大的问题是别的:( - nawfal
当然,使用TryParse的正确模式更像是 if( Guid.TryParse(s, out guid){ /* success! */ } else { /* handle invalid s */ },这就像输入值实际上可能是Guid的字符串表示的破碎示例一样,没有任何含糊之处。 - Craig
关于Guid.Parse,这个答案可能确实是正确的,但它已经错过了原始问题的全部要点。这与Guid.Parse没有任何关系,但是关于捕获Exception vs FormatException / OverflowException / etc. - Conor Gallagher


如果您可以将您的应用程序升级到C#6,那么您很幸运。新的C#版本已经实现了Exception过滤器。所以你可以这样写:

catch (Exception ex) when (ex is FormatException || ex is OverflowException) {
    WebId = Guid.Empty;
}

有些人认为这段代码是一样的

catch (Exception ex) {                
    if (ex is FormatException || ex is OverflowException) {
        WebId = Guid.Empty;
    }
    throw;
}

但事实并非如此。实际上,这是C#6中唯一不能在先前版本中模拟的新功能。首先,重新投掷意味着比跳过捕获更多的开销。其次,它在语义上不相同。在调试代码时,新功能可以保持堆栈完好无损。如果没有此功能,崩溃转储就不那么有用甚至无用了。

看到 在CodePlex上讨论这个问题。和 示例显示差异


62
2018-04-01 12:29



抛出毫无例外地保留堆栈,但“throw ex”将覆盖它。 - Ivan


如果你不想使用 if 内部声明 catch 范围, C# 6.0 您可以使用 Exception Filters 句法 CLR在预览版本中已经支持,但仅存在于 VB.NET/MSIL

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

这段代码会抓住 Exception 只有当它是一个 InvalidDataException 要么 ArgumentNullException

实际上,你可以把任何条件基本放在里面 when 条款:

static int a = 8;

...

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

请注意,而不是 if 里面的声明 catch的范围, Exception Filters 不能扔 Exceptions,当他们这样做,或当条件不是时 true, 下一个 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");
}

输出:一般捕获。

什么时候有一个以上 true  Exception Filter  - 第一个将被接受:

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 陈述,但是 Filters,和 Exceptions 不能从标有的区域内扔出 Filter 1 和 Filter 2 但过滤器扔了 Exception 将失败,也就是最后一个比较值推到堆栈之前 endfilter 命令将确定过滤器的成功/失败(Catch 1  XOR  Catch 2 将相应执行):

Exception Filters MSIL

另外,具体而言 Guid 有 Guid.TryParse 方法。


26
2017-10-07 17:31





接受的答案似乎可以接受,但CodeAnalysis /的FxCop 会抱怨它正在捕捉一般的异常类型。

此外,似乎“是”运营商可能会略微降低性能。

CA1800:不要不必要地施放 说“考虑测试'as'运算符的结果”,但是如果你这样做,你将编写的代码比单独捕获每个异常的代码要多。

无论如何,这就是我要做的:

bool exThrown = false;

try
{
    // Something
}
catch (FormatException) {
    exThrown = true;
}
catch (OverflowException) {
    exThrown = true;
}

if (exThrown)
{
    // Something else
}

19
2017-07-30 17:09



但请注意,如果您这样做,则不能在不丢失堆栈跟踪的情况下重新抛出异常。 (见Michael Stum对接受的答案的评论) - René
这种模式可以通过存储异常来改进(请原谅可怜的格式化 - 我无法弄清楚如何将代码放入注释中):Exception ex = null; try {// something} catch(FormatException e){ex = e; } catch(OverflowException e){ex = e; } if(ex!= null){//其他东西并处理ex} - Jesse Weigert
@JesseWeigert:1。你可以使用反引号为一段文本提供单行间距字体和浅灰色背景。 2.您仍然无法重新抛出原始异常 包括堆栈跟踪。 - Oliver
@CleverNeologism尽管使用它可能是真的 is 运算符可能会对性能产生轻微的负面影响,异常处理程序也不是过度关注优化性能的地方。如果您的应用在异常处理程序上花费了太多时间,那么性能优化会对应用程序性能产生真正的影响,那么还需要仔细研究其他代码问题。话虽如此,我仍然不喜欢这个解决方案,因为你丢失了堆栈跟踪,因为清理是从catch语句中上下文中删除的。 - Craig
唯一的一次 is 如果您以后执行某个操作符,则会降低性能 as 操作(因此他们将规则限定为 不必要的)。如果你所做的只是测试演员而不需要实际演员,那么 is 运算符正是您想要使用的。 - saluce


这是Matt答案的变体(我觉得这有点干净)...使用方法:

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
}

18
2017-08-31 20:51



-1这将执行 WebId = Guid.Emtpy 在没有抛出异常的情况下。 - Sepster
@sepster我认为这里暗示了“// something”之后的return语句。我不太喜欢这个解决方案,但这是讨论中的建设性变体。 +1撤消你的downvote :-) - toong
@Sepster toong是对的,我认为如果你想要回到那里,那么你会放一个...我试图让我的答案足够适用于所有情况,以防其他有类似但不是确切问题的人会受益好。但是,为了更好的衡量,我添加了一个 return我的回答。感谢您的投入。 - bsara


@Micheal

您的代码略有修改版本:

catch (Exception ex)
{
   Type exType = ex.GetType();
   if (exType == typeof(System.FormatException) || 
       exType == typeof(System.OverflowException)
   {
       WebId = Guid.Empty;
   } else {
      throw;
   }
}

字符串比较是丑陋和缓慢的。


17
2017-09-25 21:01



为什么不使用“是”关键字? - Chris Pietschmann
@Michael - 如果微软介绍,例如,从FormatException派生的StringTooLongException,它仍然是一个格式异常,只是一个特定的格式异常。这取决于您是否需要“捕获此确切异常类型”或“捕获异常(表示字符串格式错误”)的语义。 - Greg Beech
@Michael - 另外,请注意“catch(FormatException ex)具有后一种语义,它将捕获从FormatException派生的任何内容。 - Greg Beech
@Alex号没有“ex”的“throw”带有原始异常,包括原始堆栈跟踪,up。添加“ex”会使堆栈跟踪重置,因此您实际上会获得与原始异常不同的异常。我相信别人可以比我更好地解释它。 :) - Samantha Branham
-1:这段代码非常脆弱 - 库开发人员可以期待更换 throw new FormatException(); 同 throw new NewlyDerivedFromFormatException(); 没有使用库破坏代码,它将适用于所有异常处理情况,除非有人使用 == 代替 is (或简单地说 catch (FormatException))。 - Sam Harwell