一次捕获多个异常?


2140

不建议简单地抓住System.Exception。相反,仅应捕获“已知”异常。

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

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

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

给定的示例非常简单,因为它只是一个GUID。但是,请设想一下代码中多次修改对象的情况,如果其中一种操作以预期的方式失败,则您想“重置” object。但是,如果有意外的例外,我仍然想将其提高。


5
如果您使用的是.net 4及更高版本,我更喜欢使用aggregationexception msdn.microsoft.com/en-us/library/system.aggregateexception.aspx
Bepenfriends 2013年

2
Bepenfriends-由于的System.Guid不会引发AggregateException,这将是巨大的,如果你(或某人)可以发布您展示如何将它包装成一个AggregateException等一个答案

1
关于使用AggregateException在我自己的代码中引发AggregateException
DavidRR

11
“不建议仅捕获System.Exception。” -如果方法可以引发32种异常,那该怎么办?为他们每个人分别写渔获?
giorgim

5
如果一个方法抛出32种不同类型的异常,则它的编写方式很差。它要么没有捕获到自己的调用正在产生的异常,要么在一种方法中执行了过多的FAR,或者这32个中的大多数/全部应该是带有原因码的单个异常。
Flynn1179 '17

Answers:


2100

捕捉System.Exception并打开类型

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

    throw;
}

69
不幸的是,当您捕获到Exception时,FxCop(即Visual Studio代码分析)不喜欢它。
Andrew Garrison

15
我同意不捕获Exception,但是在这种情况下,捕获是一个过滤器。您可能在更高的层次上可以处理其他异常类型。我会说这是正确的,即使它包含catch(Exception x)。它不会修改程序流,它只处理某些异常,然后让应用程序的其余部分处理任何其他异常类型。
lkg

28
使用上面的代码时,最新版本的FxCop不会引发异常。
彼得

28
首先不知道OP的代码出了什么问题。排名第一的答案几乎是行数的两倍,而且可读性差得多。
若昂干萨

22
@JoãoBragança:虽然本示例中的答案使用了更多行,但请尝试想象一下您是否正在处理文件IO,例如,您想要做的就是捕获那些异常并进行一些日志消息传递,但是只有您期望的来自您的文件IO方法。然后,您通常必须处理大量(大约5个或更多)不同类型的异常。在这种情况下,这种方法可以节省一些时间。
Xilconic

594

编辑:我确实同意其他人的说法,从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块调用它。

如果您问我,第二个示例(带有ifis关键字)在项目维护阶段的可读性大大降低,同时容易出错。

对于那些相对较不熟悉编程的人来说,维护阶段将占项目整个生命周期的98.7%或更多,而执行维护工作的拙劣工作几乎可以肯定会是您以外的人。而且他们很有可能会花50%的时间在工作上骂你的名字。

当然,FxCop会吠叫您,因此您必须在代码中添加一个属性,该属性与正在运行的程序具有精确的zip关系,仅是要告诉FxCop忽略一个问题,即99.9%的情况是完全正确标记。而且,对不起,我可能会弄错,但是,“ ignore”属性最终是否最终会编译到您的应用程序中?

将整个if测试放在一行上会使其更具可读性吗?我不这么认为。我的意思是,很久以前,确实有另一位程序员激烈地争辩说,将更多代码放在一行上会使其“运行得更快”。但是他当然是疯了。试图向他解释(直截了当-这很有挑战性)解释器或编译器如何将长行拆分成离散的每行一条指令-与他继续前进时的结果基本相同只是使代码具有可读性,而不是试图使编译器不那么聪明-对其没有任何影响。但是我离题了。

当您再添加三种异常类型(从现在开始的一两个月)时,可读性降低了多少?(答案:它得到了很多的可读性)。

实际上,主要要点之一是格式化我们每天都在看的文本源代码的大部分要点是使它真正,对于其他人来说真的很明显,这是代码运行时实际发生的事情。因为编译器将源代码转换成完全不同的东西,所以不必关心您的代码格式样式。因此,全线式也很烂。

只是说...

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

36
当我第一次偶然发现这个问题时,我已经接受了所有答案。很酷,我可以捕获所有Exceptions并检查类型。我以为它可以清理代码,但是有些事情使我回想起这个问题,而实际上我读了该问题的其他答案。我咀嚼了一会儿,但我必须同意你的看法。它是更多的可读性和可维护性使用功能,而不是捕捉一切干你的代码,核对清单,包装代码,并投掷比较类型。感谢您来晚了,并提供了其他替代方法(IMO)。+1。
错误的2014年

8
如果您要包含,则无法使用错误处理功能throw;。您必须在每个catch块中重复该行代码(显然不是世界末日,但值得一提,因为它是需要重复的代码)。
kad81

5
@ kad81是的,但是您仍然会受益于将日志记录和清理代码放在一个位置,并在需要的地方在一个位置进行更改,而无需捕获基本的Exception类型然后根据异常类型。而且,throw();在每个catch块中多出一条语句需要付出的代价很小,IMO,如果需要的话,您仍然可以做其他特定于异常类型的清理。
克雷格

2
@Reitffunk,您好,只需使用Func<Exception, MyEnumType>代替即可Action<Exception>。这就是Func<T, Result>,与Result作为返回类型。
Craig

3
我完全同意。我也读了第一个答案,认为似乎合乎逻辑。移动到所有异常处理程序的通用1。我内心的某种东西使我内部产生了恶心……所以我还原了代码。然后遇到了这个美女!这需要成为公认的答案
Conor Gallagher 2015年

372

正如其他人指出的那样,您可以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编译器的特定版本(而不是任何系统安装的版本)来构建项目。


3
耐心等待6的正式发布...我希望看到这种情况出现时,它会变得很麻烦。
RubberDuck

@RubberDuck我渴望使用C#6中的null传播运算符。试图说服我团队的其余成员相信使用不稳定的语言/编译器是值得的。许多小的改进,产生了巨大的影响。至于被标记为答案,并不重要,只要人们意识到这将是可能的,我就很高兴。

对?!我将在不久的将来很好地查看我的代码库。=)我知道检查并不重要,但是鉴于可接受的答案将很快过时,因此我希望OP能够再次检查它以使其具有适当的可见性。
RubberDuck

这就是为什么我还没有授予@Joe的部分原因。我希望这是可见的。为了清楚起见,您可能想添加一个内联过滤器的示例。
RubberDuck

188

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

26
有趣的想法和另一个示例,表明VB.net有时比C#具有一些有趣的优势
Michael Stum

47
@MichaelStum与种语法我很难把它在所有...有趣不寒而栗
MarioDS

17
C#6中引入了异常过滤器!请注意使用过滤器以重新抛出roslyn.codeplex.com/discussions/541301
Arne Deruwe 2014年

@ArneDeruwe谢谢您的链接!我刚刚得知一个更重要的原因不能再掷:throw e;破阵堆栈跟踪调用堆栈,throw;破阵“只有”调用堆栈(渲染崩溃转储没用!)一个非常好的理由来使用既不是否可以避免!
AnorZaken 2014年

1
从C#开始,有6个异常过滤器可用!最后。
丹尼

134

为了完整起见,从.NET 4.0开始,代码可以重写为:

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

TryParse绝不会引发异常,如果格式错误,则将WebId设置为,则返回false Guid.Empty


C#7开始,您可以避免在单独的行上引入变量:

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

您还可以创建用于解析返回元组的方法,该方法在4.6版以上的.NET Framework中尚不可用:

(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中实现析构参数的解构时,对这个无用的答案的下一个无用的更新就会到来。


19
精确-简洁,您完全绕过了处理异常的性能损失,故意使用异常来控制程序流的不良形式,以及使转换逻辑四处分散的软焦点。 。
Craig

9
我知道你的意思,但是当然Guid.TryParse永远不会回来Guid.Empty。如果字符串格式错误,则将result输出参数设置为Guid.Empty,但返回 false。我之所以提及它,是因为我看过以样式进行工作的代码,Guid.TryParse(s, out guid); if (guid == Guid.Empty) { /* handle invalid s */ }如果s可能是的字符串表示形式,通常是错误的Guid.Empty

14
哇,您已经回答了这个问题,只是它不符合该问题的精神。更大的问题是其他问题:(
nawfal

6
当然,使用TryParse的正确模式更像if( Guid.TryParse(s, out guid){ /* success! */ } else { /* handle invalid s */ },它不会像残破的示例那样含糊不清,在该示例中,输入值实际上可能是Guid的字符串表示形式。
克雷格2014年

2
对于Guid.Parse,此答案可能确实是正确的,但它遗漏了原始问题的全部内容。这与Guid.Parse无关,但是与捕获Exception vs FormatException / OverflowException / etc有关。
Conor Gallagher 2015年

114

现在,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..? 
}

首选此方法,不仅因为它简单明了,而且在不满足条件的情况下也不必解开堆栈,与重新抛出相比,它提供了更好的性能和诊断信息。

74

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

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上查看有关此内容讨论。并举例说明差异


4
没有异常的抛出将保留堆栈,但是“ throw ex”将覆盖它。
伊凡(Ivan)

32

如果你不希望使用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仅在a InvalidDataException或时捕获ArgumentNullException

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

static int a = 8;

...

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

请注意,与范围if内的语句相反catchException 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将相应执行):

异常过滤器MSIL

另外,具体GuidGuid.TryParse方法。


+1用于显示多个过滤器,并提供使用多个过滤器时的解释。
steven87vt 18-10-9

26

使用C#7 可以改善Michael Stum的答案,同时保持switch语句的可读性:

catch (Exception ex)
{
    switch (ex)
    {
        case FormatException _:
        case OverflowException _:
            WebId = Guid.Empty;
            break;
        default:
            throw;
    }
}

并使用C#8作为开关表达式:

catch (Exception ex)
{
    WebId = ex switch
    {
        _ when ex is FormatException || ex is OverflowException => Guid.Empty,
        _ => throw ex
    };
}

3
自2018年恕我直言,这应该是公认的答案。
MemphiZ

6
Mat J的答案使用起来when比开关更优雅/更合适。
rgoliveira

@rgoliveira:我同意对于问题中提出的案例,Mat J的回答更为优雅和恰当。但是,如果您要根据异常类型执行不同的代码,或者想要实际使用异常的实例,将很难阅读。使用switch语句可以平等地对待所有这些情况。
Fabian

1
@Fabian“如果您要根据异常类型执行不同的代码,或者如果您想实际使用异常的实例”,那么您只需创建一个不同的catch块,或者仍然需要强制转换它。根据我的经验,throw;您所遇到的问题catch可能是代码气味。
rgoliveira

@rgoliveira:在某些情况下可以在catch块中使用抛出法,请参见link。由于case语句实际上使用了模式匹配链接,因此,如果用变量名替换抛弃运算符链接(下划线),则不需要强制转换。别误会,我同意您的观点,即异常过滤器是一种更清洁的方法,但是多个catch块会添加很多花括号。
Fabian

20

可接受的答案似乎是可以接受的,除了CodeAnalysis / FxCop会抱怨它正在捕获通用异常类型这一事实。

另外,似乎“ is”运算符可能会稍微降低性能。

CA1800:不必进行不必要的强制转换,而是“考虑测试'as'运算符的结果”,但是,如果这样做的话,与单独捕获每个异常的情况相比,您将编写更多的代码。

无论如何,这是我会做的:

bool exThrown = false;

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

if (exThrown)
{
    // Something else
}

19
但是请注意,如果您这样做,就不能在不丢失堆栈跟踪的情况下抛出异常。(见迈克尔葡萄汁对接受的答案评论)
勒内·

2
可以通过存储异常来改善这种模式(请原谅格式不正确的原因-我不知道如何将代码放入注释中):Exception ex = null; 尝试{//}} catch(FormatException e){ex = e; } catch(OverflowException e){ex = e; } if(ex!= null){//其他东西并处理ex}
Jesse Weigert 2012年

3
@JesseWeigert:1.您可以使用反引号为文本赋予等宽字体和浅灰色背景。2.您仍然无法重新抛出包括stacktrace在内的原始异常。
奥利弗(Oliver)2012年

2
@CleverNeologism尽管使用is运算符可能确实对性能有轻微的负面影响,但也确实不是异常处理程序不是过度关注优化性能的地方。如果您的应用程序在异常处理程序上花费了太多时间,以至于性能优化将对应用程序性能产生真正的影响,那么还有其他代码问题需要仔细研究。话虽如此,我仍然不喜欢这种解决方案,因为您丢失了堆栈跟踪并且因为清除是从catch语句上下文中删除的。
Craig 2015年

3
is操作员唯一会降低性能的是您以后执行某项as操作(因此,他们不必要地限定了规则)。如果您要做的只是测试转换而无需实际执行转换,那么is操作员正是您要使用的。
2015年

19

在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");
 }

18

这是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
}

-1 WebId = Guid.Emtpy在不引发异常的情况下执行。
Sepster 2012年

4
@sepster我认为这里暗含“ // something”之后的return语句。我不太喜欢该解决方案,但这是讨论中的一个建设性变体。+1撤消您的
下注

@Sepster toong是正确的,我认为如果您想在那里退货,那您可以放一个...我试图使我的回答足够普遍,以适用于所有情况,以防其他有类似但不完全相同的问题的人受益,因为好。但是,为了很好,我在return答案中添加了一个。感谢您的输入。
bsara

18

约瑟夫·戴格(Joseph Daigle)的“答案”是一个很好的解决方案,但我发现以下结构较为简洁且不易出错。

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

    // Handle exception
}

反转表达式有一些优点:

  • 不需要返回声明
  • 该代码未嵌套
  • 不必忘记在约瑟夫解决方案中将“ throw”或“ return”语句与表达式分开的陈述。

它甚至可以压缩为一行(尽管不是很漂亮)

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
}

2
+1,这是最好的答案。它比顶部的答案更好,主要是因为没有答案return,尽管反转条件也要好一些。
DCShannon

我什至没有想到这一点。很好,我将其添加到列表中。
Stefan T

16

@米歇尔

您的代码略有修改:

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

字符串比较丑陋且缓慢。


21
为什么不只使用“ is”关键字?
克里斯·彼得施曼

29
@Michael-如果微软引入了从FormatException派生的StringTooLongException,那么它仍然是格式异常,只是一个特定的异常。这取决于您是想要“捕获此确切的异常类型”还是“捕获表示字符串格式错误的异常”的语义。
格雷格·比奇

6
@迈克尔-此外,请注意“抓(出现FormatException前)有后一种语义,它会赶上从出现FormatException派生什么。
格雷格山毛榉

14
@Alex号“ throw”(不带“ ex”)带有原始异常,包括原始堆栈跟踪。添加“ ex”会使堆栈跟踪复位,因此您实际上会获得与原始异常不同的异常。我敢肯定有人会比我更好地解释它。:)
萨曼莎·布拉纳姆

13
-1:此代码非常脆弱-库开发人员可能希望在不破坏使用该库的代码的情况下替换throw new FormatException();throw new NewlyDerivedFromFormatException();,并且对于所有异常处理情况都适用,除非有人==代替is(或简单地catch (FormatException))使用。
山姆·哈威尔

13

怎么样

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

仅当可以将捕获代码完全移入“尝试块”时,此方法才有效。但是,对您对一个对象进行多次操作并在其中进行一次操作的映像代码失败了,并且您想“重置”该对象。
迈克尔·斯托姆

4
在那种情况下,我将添加一个重置函数并从多个catch块中调用它。
莫里斯

12
catch (Exception ex)
{
    if (!(
        ex is FormatException ||
        ex is OverflowException))
    {
        throw;
    }
    Console.WriteLine("Hello");
}

11

注意和警告:另一种功能样式。

链接中的内容不能直接回答您的问题,但是将其扩展为以下形式很简单:

static void Main() 
{ 
    Action body = () => { ...your code... };

    body.Catch<InvalidOperationException>() 
        .Catch<BadCodeException>() 
        .Catch<AnotherException>(ex => { ...handler... })(); 
}

(基本上提供了另一个空的Catch过载,它会返回自身)

更大的问题是为什么。我认为成本不会超过收益:)


1
这种方法的一个可能优点是,在捕获和重新抛出异常与不捕获异常之间存在语义差异。在某些情况下,代码应该对异常采取行动而不捕获它。在vb.net中可能会发生这种情况,但在C#中则不可能,除非有人使用由vb.net编写并从C#调用的包装器。
超级猫

1
如何在不捕获异常的情况下采取行动?我不完全了解你。
nawfal

@nawful ...使用vb过滤器-函数filt(ex作为例外):LogEx(ex):返回false ...然后在捕获行中:当filt(ex)时捕获ex
FastAl 2015年

1
@FastAl这不是C#6中允许的异常过滤器吗?
HimBromBeere

@HimBromBeere是的,它们是直接类似物
FastAl

9

2015年12 月15日更新:有关C#6,请参阅https://stackoverflow.com/a/22864936/1718702。它是一种更清洁的语言,现在是该语言的标准。

针对希望使用更优雅的解决方案捕获一次并过滤异常的用户,我使用了一种扩展方法,如下所示。

我已经在我的库中有了这个扩展,最初是为其他目的而编写的,但是它对于type检查异常非常有效。另外,恕我直言,它看起来比一堆||声明还干净。另外,与接受的答案不同,我更喜欢显式异常处理,因此ex is ...具有不希望的行为,因为派生的类可以分配给那里的父类型。

用法

if (ex.GetType().IsAnyOf(
    typeof(FormatException),
    typeof(ArgumentException)))
{
    // Handle
}
else
    throw;

IsAnyOf.cs扩展(有关依赖关系,请参见完整的错误处理示例)

namespace Common.FluentValidation
{
    public static partial class Validate
    {
        /// <summary>
        /// Validates the passed in parameter matches at least one of the passed in comparisons.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="p_parameter">Parameter to validate.</param>
        /// <param name="p_comparisons">Values to compare against.</param>
        /// <returns>True if a match is found.</returns>
        /// <exception cref="ArgumentNullException"></exception>
        public static bool IsAnyOf<T>(this T p_parameter, params T[] p_comparisons)
        {
            // Validate
            p_parameter
                .CannotBeNull("p_parameter");
            p_comparisons
                .CannotBeNullOrEmpty("p_comparisons");

            // Test for any match
            foreach (var item in p_comparisons)
                if (p_parameter.Equals(item))
                    return true;

            // Return no matches found
            return false;
        }
    }
}

完整的错误处理示例(复制粘贴到新的控制台应用程序)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Common.FluentValidation;

namespace IsAnyOfExceptionHandlerSample
{
    class Program
    {
        static void Main(string[] args)
        {
            // High Level Error Handler (Log and Crash App)
            try
            {
                Foo();
            }
            catch (OutOfMemoryException ex)
            {
                Console.WriteLine("FATAL ERROR! System Crashing. " + ex.Message);
                Console.ReadKey();
            }
        }

        static void Foo()
        {
            // Init
            List<Action<string>> TestActions = new List<Action<string>>()
            {
                (key) => { throw new FormatException(); },
                (key) => { throw new ArgumentException(); },
                (key) => { throw new KeyNotFoundException();},
                (key) => { throw new OutOfMemoryException(); },
            };

            // Run
            foreach (var FooAction in TestActions)
            {
                // Mid-Level Error Handler (Appends Data for Log)
                try
                {
                    // Init
                    var SomeKeyPassedToFoo = "FooParam";

                    // Low-Level Handler (Handle/Log and Keep going)
                    try
                    {
                        FooAction(SomeKeyPassedToFoo);
                    }
                    catch (Exception ex)
                    {
                        if (ex.GetType().IsAnyOf(
                            typeof(FormatException),
                            typeof(ArgumentException)))
                        {
                            // Handle
                            Console.WriteLine("ex was {0}", ex.GetType().Name);
                            Console.ReadKey();
                        }
                        else
                        {
                            // Add some Debug info
                            ex.Data.Add("SomeKeyPassedToFoo", SomeKeyPassedToFoo.ToString());
                            throw;
                        }
                    }
                }
                catch (KeyNotFoundException ex)
                {
                    // Handle differently
                    Console.WriteLine(ex.Message);

                    int Count = 0;
                    if (!Validate.IsAnyNull(ex, ex.Data, ex.Data.Keys))
                        foreach (var Key in ex.Data.Keys)
                            Console.WriteLine(
                                "[{0}][\"{1}\" = {2}]",
                                Count, Key, ex.Data[Key]);

                    Console.ReadKey();
                }
            }
        }
    }
}

namespace Common.FluentValidation
{
    public static partial class Validate
    {
        /// <summary>
        /// Validates the passed in parameter matches at least one of the passed in comparisons.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="p_parameter">Parameter to validate.</param>
        /// <param name="p_comparisons">Values to compare against.</param>
        /// <returns>True if a match is found.</returns>
        /// <exception cref="ArgumentNullException"></exception>
        public static bool IsAnyOf<T>(this T p_parameter, params T[] p_comparisons)
        {
            // Validate
            p_parameter
                .CannotBeNull("p_parameter");
            p_comparisons
                .CannotBeNullOrEmpty("p_comparisons");

            // Test for any match
            foreach (var item in p_comparisons)
                if (p_parameter.Equals(item))
                    return true;

            // Return no matches found
            return false;
        }

        /// <summary>
        /// Validates if any passed in parameter is equal to null.
        /// </summary>
        /// <param name="p_parameters">Parameters to test for Null.</param>
        /// <returns>True if one or more parameters are null.</returns>
        public static bool IsAnyNull(params object[] p_parameters)
        {
            p_parameters
                .CannotBeNullOrEmpty("p_parameters");

            foreach (var item in p_parameters)
                if (item == null)
                    return true;

            return false;
        }
    }
}

namespace Common.FluentValidation
{
    public static partial class Validate
    {
        /// <summary>
        /// Validates the passed in parameter is not null, throwing a detailed exception message if the test fails.
        /// </summary>
        /// <param name="p_parameter">Parameter to validate.</param>
        /// <param name="p_name">Name of tested parameter to assist with debugging.</param>
        /// <exception cref="ArgumentNullException"></exception>
        public static void CannotBeNull(this object p_parameter, string p_name)
        {
            if (p_parameter == null)
                throw
                    new
                        ArgumentNullException(
                        string.Format("Parameter \"{0}\" cannot be null.",
                        p_name), default(Exception));
        }
    }
}

namespace Common.FluentValidation
{
    public static partial class Validate
    {
        /// <summary>
        /// Validates the passed in parameter is not null or an empty collection, throwing a detailed exception message if the test fails.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="p_parameter">Parameter to validate.</param>
        /// <param name="p_name">Name of tested parameter to assist with debugging.</param>
        /// <exception cref="ArgumentNullException"></exception>
        /// <exception cref="ArgumentOutOfRangeException"></exception>
        public static void CannotBeNullOrEmpty<T>(this ICollection<T> p_parameter, string p_name)
        {
            if (p_parameter == null)
                throw new ArgumentNullException("Collection cannot be null.\r\nParameter_Name: " + p_name, default(Exception));

            if (p_parameter.Count <= 0)
                throw new ArgumentOutOfRangeException("Collection cannot be empty.\r\nParameter_Name: " + p_name, default(Exception));
        }

        /// <summary>
        /// Validates the passed in parameter is not null or empty, throwing a detailed exception message if the test fails.
        /// </summary>
        /// <param name="p_parameter">Parameter to validate.</param>
        /// <param name="p_name">Name of tested parameter to assist with debugging.</param>
        /// <exception cref="ArgumentException"></exception>
        public static void CannotBeNullOrEmpty(this string p_parameter, string p_name)
        {
            if (string.IsNullOrEmpty(p_parameter))
                throw new ArgumentException("String cannot be null or empty.\r\nParameter_Name: " + p_name, default(Exception));
        }
    }
}

两个NUnit单元测试样本

Exception类型的匹配行为是精确的(即,子代与其父代类型都不匹配)。

using System;
using System.Collections.Generic;
using Common.FluentValidation;
using NUnit.Framework;

namespace UnitTests.Common.Fluent_Validations
{
    [TestFixture]
    public class IsAnyOf_Tests
    {
        [Test, ExpectedException(typeof(ArgumentNullException))]
        public void IsAnyOf_ArgumentNullException_ShouldNotMatch_ArgumentException_Test()
        {
            Action TestMethod = () => { throw new ArgumentNullException(); };

            try
            {
                TestMethod();
            }
            catch (Exception ex)
            {
                if (ex.GetType().IsAnyOf(
                    typeof(ArgumentException), /*Note: ArgumentNullException derrived from ArgumentException*/
                    typeof(FormatException),
                    typeof(KeyNotFoundException)))
                {
                    // Handle expected Exceptions
                    return;
                }

                //else throw original
                throw;
            }
        }

        [Test, ExpectedException(typeof(OutOfMemoryException))]
        public void IsAnyOf_OutOfMemoryException_ShouldMatch_OutOfMemoryException_Test()
        {
            Action TestMethod = () => { throw new OutOfMemoryException(); };

            try
            {
                TestMethod();
            }
            catch (Exception ex)
            {
                if (ex.GetType().IsAnyOf(
                    typeof(OutOfMemoryException),
                    typeof(StackOverflowException)))
                    throw;

                /*else... Handle other exception types, typically by logging to file*/
            }
        }
    }
}

1
增强语言并不是 “更加优雅”。实际上,这在许多地方造成了维护地狱。多年后,许多程序员都不为他们创造的怪物感到骄傲。这不是您惯于阅读的内容。可能会引起“呵呵”?效果,甚至是严重的“ WTF”。有时会造成混乱。它唯一要做的就是使那些在以后的维护中需要处理的代码变得更难掌握-仅仅是因为一个程序员试图变得“聪明”。多年以来,我了解到那些“聪明”的解决方案很少也是好的解决方案。
Kaii 2014年

1
或用几句话来表达:坚持使用该语言本地提供的可能性。不要仅仅因为不喜欢语言的语义就尝试覆盖语言的语义。老实说,您的同事(可能还有我)。
Kaii 2014年

还要注意,您的解决方案仅会近似C#6的语义when,就像任何版本的catch (Exception ex) {if (...) {/*handle*/} throw;}。的真正价值when在于过滤器在捕获异常之前运行,从而避免了重新抛出的开销/堆栈损坏。它利用了VB和MSIL以前只能使用的CLR功能。
Marc L.

更优雅?这个例子对于这样一个简单的问题是如此之大,并且代码看起来太糟糕了,甚至都不值得一看。请不要在实际项目中使此代码成为其他人的问题。
KthProg '16

您的整个IsAnyOf方法可以简单地改写p_comparisons.Contains(p_parameter)
maksymiuk

7

由于我觉得这些答案刚刚触及表面,因此我尝试进行更深入的研究。

因此,我们真正想要做的是未编译的内容,例如:

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

我们之所以想要这样做,是因为我们不希望异常处理程序捕获过程中稍后需要的内容。当然,我们可以捕获一个Exception并用“ if”检查该怎么做,但是老实说,我们并不是真的想要那个。(FxCop,调试器问题,丑陋)

那么为什么不编译该代码-以及我们如何才能以这种方式对其进行破解?

如果我们看一下代码,我们真正想做的就是转发呼叫。但是,根据MS Partition II,IL异常处理程序块将无法像这样工作,在这种情况下,这是有意义的,因为这意味着'exception'对象可以具有不同的类型。

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

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

无法编译的原因非常明显:“ $ exception”对象将具有什么类型和值(它们存储在变量“ e”中)?我们希望编译器处理此问题的方式是,注意两个异常的通用基本类型是“ Exception”,将其用于包含两个异常的变量,然后仅处理捕获到的两个异常。在IL中实现此方法的方式称为“过滤器”,在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'对象。

此时,我们可以添加一个单独的函数,该函数使用重载解析来处理不同类型的Exception或处理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;
    }
}

因此得出结论:

  • 如果我们不想重新抛出,我们可能会考虑捕获正确的异常,并将其存储在临时中。
  • 如果处理程序很简单,并且我们想重用代码,则最好的解决方案可能是引入一个辅助函数。
  • 如果我们想重新抛出,我们别无选择,只能将代码放入“ Exception”捕获处理程序中,这将破坏FxCop和调试器未捕获的异常。

7

这是每个C#开发人员最终都会面对的经典问题。

让我将您的问题分为两个问题。首先,

我可以一次捕获多个异常吗?

简而言之,没有。

导致下一个问题,

考虑到我无法在同一catch()块中捕获多个异常类型,如何避免编写重复的代码?

给定您的特定样本,其中回退值的构建成本很低,我喜欢遵循以下步骤:

  1. 将WebId初始化为后备值。
  2. 在一个临时变量中构造一个新的Guid。
  3. 将WebId设置为完全构造的临时变量。使它成为try {}块的最终声明。

因此,代码如下所示:

try
{
    WebId = Guid.Empty;
    Guid newGuid = new Guid(queryString["web"]);
    // More initialization code goes here like 
    // newGuid.x = y;
    WebId = newGuid;
}
catch (FormatException) {}
catch (OverflowException) {}

如果引发任何异常,则WebId永远不会设置为一半构造的值,并保持Guid.Empty。

如果构造回退值很昂贵,而重置值便宜得多,则可以将重置代码移至其自己的函数中:

try
{
    WebId = new Guid(queryString["web"]);
    // More initialization code goes here.
}
catch (FormatException) {
    Reset(WebId);
}
catch (OverflowException) {
    Reset(WebId);
}

这是很好的“生态编码”,即您正在考虑代码和数据占用空间,并确保不会泄漏一半已处理值。杰弗里(Jeffrey)很高兴遵循这种模式!
塔希尔·哈立德

6

因此,您在每个异常开关内重复很多代码吗?听起来像是提取一种方法会是个主意,不是吗?

因此,您的代码可以归结为:

MyClass instance;
try { instance = ... }
catch(Exception1 e) { Reset(instance); }
catch(Exception2 e) { Reset(instance); }
catch(Exception) { throw; }

void Reset(MyClass instance) { /* reset the state of the instance */ }

我不知道为什么没人注意到这种代码重复。

在C#6中,您还拥有其他人已经提到的异常过滤器。因此,您可以修改上面的代码:

try { ... }
catch(Exception e) when(e is Exception1 || e is Exception2)
{ 
    Reset(instance); 
}

3
“我想知道为什么没人注意到这种代码重复。” -嗯?问题的全部重点是消除代码重复。
马克·阿默里

4

想要在这个已经很长的线程中添加我的简短答案。尚未提及的是catch语句的优先顺序,更具体地说,您需要了解要尝试捕获的每种异常的范围。

例如,如果您使用“ catch-all”异常作为Exception,它将优先于所有其他catch语句,并且显然会出现编译器错误,但是如果您颠倒顺序,则可以链接您的catch语句(我认为是反模式的位) ),您可以将“全部捕获的异常”类型放在底部,这将捕获在try..catch块中无法满足更高要求的所有异常:

            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文档:

异常层次


4

也许尝试使代码保持简单,例如将通用代码放入方法中,就像处理不在catch子句中的其他代码一样?

例如:

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

// ...

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

我会怎么做,试图找到简单的就是美丽的图案


3

请注意,我确实找到了一种方法来执行此操作,但这看起来更像是The Daily WTF的资料

catch (Exception ex)
{
    switch (ex.GetType().Name)
    {
        case "System.FormatException":
        case "System.OverflowException":
            WebId = Guid.Empty;
            break;
        default:
            throw;
    }
}

9
-1票,+ 5 WTF :-)不应将其标记为答案,但这很有趣。
亚伦,

1
没关系,我们可以简单地做到这一点。但是他并没有闲着,而是提出了解决方案。非常感谢。
Maxymus '16

2
但是,实际上不要执行此操作,请在C#6或任何其他答案中使用异常过滤器-我在这里专门将其指定为“这是一种方法,但这很不好,我想做得更好”。
Michael Stum

为什么这样不好?我很困惑,您不能直接在switch语句中使用该异常。
MKesper '16

3
@MKesper我知道它不好的一些原因。它要求将完全合格的类名称编写为字符串文字,这容易受到拼写错误的影响,而这些错误是编译器无法挽救您的。(这很重要,因为在许多商店中,错误案例的检验程度较低,因此,它们中的小错误更容易被遗漏。)它也将无法匹配作为指定案例之一的子类的异常。而且,由于是字符串,因此VS的“查找所有引用”之类的工具会漏掉这些情况-如果要在发现特定异常的地方添加清理步骤,则可以使用。
马克·阿默里

2

这里值得一提。您可以响应多个组合(异常错误和exception.message)。

当尝试将控件对象转换为TextBox,TextBlock或CheckBox内容的数据网格时,我遇到了一个用例场景。在这种情况下,返回的Exception是相同的,但是消息有所不同。

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
} 

0

我想建议最简短的答案(一种功能样式):

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

ps为了简化代码,我没有进行空检查,请考虑添加参数验证。

ps2如果要从catch中返回一个值,则必须执行相同的Catch方法,但要使用returns和Func而不是参数中的Action。


-15

只需拨打try和catch两次。

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

就这么简单!!


3
嗯 这违反了问题的目的。他问这个问题以消除重复的代码。此答案添加了更多重复的代码。
詹姆斯·艾什

-23

在c#6.0中,异常过滤器是对异常处理的改进

try
{
    DoSomeHttpRequest();
}
catch (System.Web.HttpException e)
{
    switch (e.GetHttpCode())
    {
        case 400:
            WriteLine("Bad Request");
        case 500:
            WriteLine("Internal Server Error");
        default:
            WriteLine("Generic Error");
    }
}

13
本示例未显示任何异常过滤器的使用。
user247702 2015年

这是标准的方式来过滤例外,在C#6.0
卡希夫

5
再次看看到底什么是异常过滤器。您没有在示例中使用异常过滤器。在您的答案之前一年发布的答案中有一个适当的例子。
user247702 2015年

6
异常过滤的一个例子是catch (HttpException e) when e.GetHttpCode() == 400 { WriteLine("Bad Request"; }
2015年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.