当前位置:  开发笔记 > 编程语言 > 正文

一次捕获多个异常?

如何解决《一次捕获多个异常?》经验,为你挑选了21个好方法。

不鼓励简单地抓住System.Exception.相反,只应捕获"已知"异常.

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

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

我想知道:有没有办法捕获两个异常并且只WebId = Guid.Empty调用一次呼叫?

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



1> Joseph Daigl..:

抓住System.Exception并打开类型

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

    throw;
}


不幸的是,当您捕获异常时,FxCop(即 - Visual Studio代码分析)不喜欢它.
使用上面的代码时,最新版本的FxCop不会抛出异常.
首先不确定OP的代码有什么问题.#1接受的答案几乎是行数的两倍,可读性差得多.
@JoãoBragança:虽然这个例子中的答案使用了更多行,但试着想象一下你是否正在处理文件IO,而你想要做的就是捕获这些异常并做一些日志消息,但只有那些你希望来自你的文件IO方法.然后,您经常需要处理更多(大约5个或更多)不同类型的异常.在这种情况下,这种方法可以为您节省一些线路.
我同意不捕获异常,但是,在这种情况下,catch是一个过滤器.您可能有一个更高的层来处理其他异常类型.我会说这是正确的,即使它包含一个catch(Exception x).它不会修改程序流,它只处理某些异常,然后让应用程序的其余部分处理任何其他异常类型.
该答案已过时,请注意以下乔的答案:/sf/ask/17360801/
@David来自FxCop(和类似产品)的警告更多的是你所谓的*指导方针*而不是实际规则...如果你知道它为什么会导致问题并且你正在采取措施纠正它(比如适当地重新投掷).*盲目地*捕获"异常"是不好的,因为它可以无声地吞下你不期望的错误,这使得调试非常困难.此代码将重新抛出任何不是我们所追求的特定错误类型的内容,因此它是安全的.
@slypete我认为"回归" 是不必要的,这将使其他正确.
不应该是:else {throw ex; }?
如果你必须这样做,OP中的代码不会更好吗?
我想我的观点是在原始示例中,函数不一定在try/catch之后结束,如果你在WebId = Guid.Empty之后放回一个函数,那么函数将退出并且try/catch之后没有代码将被执行.
还晚到了聚会。不知道人们从哪里获得呼叫堆栈和呼叫跟踪信息,但是使用`throw`不会覆盖呼叫堆栈或跟踪,而`throw ex`会覆盖呼叫堆栈。
但是,如果您只是捕获异常而不是更专业的异常,则FxCop会显示警告"不要捕获常规异常类型".因此,这不是我的选择.
正如[此注释](http://stackoverflow.com/questions/136035/catch-multiple-exceptions-at-once#comment42795383_136114)所指出的那样,在这里`throw;`将会破坏调用栈,使崩溃转储无用。 !**非常**的理由不去偷懒地抓很多东西!(`throw ex;`破坏了调用栈和栈跟踪。)

2> Craig..:

编辑:我同意其他人的意见,从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 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%或更多,并且做维护的穷人schmuck几乎肯定会成为除你以外的其他人.并且他们很有可能将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;
}


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

3> Joe..:

正如其他人所指出的那样,你可以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编译器构建项目,而不是任何系统安装版本.


耐心地等待6的官方发布......我希望看到这种情况发生时会发生变化.

4> Greg Beech..:

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


@MichaelStum用**那种语法我根本不称它有趣......*不寒而栗*
有趣的想法和另一个例子,VB.net有时比C#有一些有趣的优势
异常过滤器将在c#6中出现!请注意使用过滤器的区别,有利于重新抛出https://roslyn.codeplex.com/discussions/541301

5> Athari..:

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

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

TryParse永远不会抛出异常,如果格式错误则返回false,将WebId设置为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中实现out-parameters的解构时,接下来对这个无用的答案进行无用的更新.:)


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

6> Mat J..:

现在,c#6+中提供了异常过滤器.你可以做

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



7> Maniero..:

如果您可以将您的应用程序升级到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上有关此问题的讨论.一个显示差异的例子.


抛出毫无例外地保留堆栈,但"throw ex"将覆盖它.

8> Tamir Vered..:

如果你不希望使用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内的语句相反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 1Filter 2,但过滤器抛Exception反而会失败,也是最后的比较值被推到堆栈中前endfilter命令将确定过滤器的成功/失败(Catch 1 XOR Catch 2将相应地执行):

例外过滤器MSIL

另外,具体GuidGuid.TryParse方法.



9> Fabian..:

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

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


Mat J使用“ when”的答案远比开关更优雅/合适。
这应该是2018年恕我直言的公认答案.

10> Matt..:

接受的答案似乎是可以接受的,除了CodeAnalysis/FxCop会抱怨它正在捕获一般异常类型.

此外,似乎"是"运营商可能会略微降低性能.

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

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

bool exThrown = false;

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

if (exThrown)
{
    // Something else
}


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

11> SHM..:

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



12> bsara..:

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


@sepster我认为这里暗示了"// something"之后的return语句.我不太喜欢这个解决方案,但这是讨论中的建设性变体.+1撤消你的downvote :-)

13> Stefan T..:

Joseph Daigle的答案是一个很好的解决方案,但我发现以下结构有点整洁且不易出错.

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

    // Handle exception
}

反转表达式有一些优点:

不需要退货声明

代码不是嵌套的

忘记在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
}


+1,这是最好的答案.它比最佳答案更好,主要是因为没有"返回",尽管反转条件也好一点.

14> FlySwat..:

@Micheal

您的代码略有修改版本:

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

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


@Michael - 如果Microsoft引入了,例如,从FormatException派生的StringTooLongException,那么它仍然是一个格式异常,只是一个特定的格式异常.这取决于你是否想要"捕获这个确切的异常类型"或"捕获表示字符串格式错误的异常"的语义.
为什么不使用"是"关键字?
@Alex号没有"ex"的"throw"带有原始异常,包括原始堆栈跟踪,up.添加"ex"会使堆栈跟踪重置,因此您实际上会获得与原始异常不同的异常.我相信其他人可以比我更好地解释它.:)
-1:这段代码非常脆弱 - 库开发人员可以期望用`throw new NewlyDerivedFromFormatException();`替换`throw new FormatException();`而不破坏使用库的代码,并且它将适用于所有异常处理案例除非有人使用`==`而不是`is`(或只是`catch(FormatException)`).
@Michael - 另外,请注意"catch(FormatException ex)具有后一种语义,它将捕获从FormatException派生的任何内容.
如果您首先捕获异常,我不明白为什么这种对微观优化的常见痴迷是相关的.
@FlySwat:我做了大量的反思工作 - 主要与实体映射有关.`is`和`GetType()==`都是运行时操作.编译器都没有实现.但是,`is`比比较类型对象快得多.`GetType()`实际上是一种非常慢的方法,而`is`可以通过JIT进行优化.
这两个检查具有不同的语义.'=='检查是精确类型检查,而'是'检查是可分配性检查.我认为后者在这种情况下更合适.

15> Maurice..:

怎么样

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


在这种情况下,我会添加一个重置函数,并从多个catch块调用它.

16> Konstantin S..:
catch (Exception ex)
{
    if (!(
        ex is FormatException ||
        ex is OverflowException))
    {
        throw;
    }
    Console.WriteLine("Hello");
}



17> nawfal..:

警告和警告:另一种功能风格.

链接中的内容不能直接回答您的问题,但将其扩展为以下内容是微不足道的:

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

    body.Catch() 
        .Catch() 
        .Catch(ex => { ...handler... })(); 
}

(基本上提供另一个自动Catch返回的空重载)

对此更大的问题是为什么.我不认为成本超过这里的收益:)



18> HodlDwon..:

更新2015-12-15:有关C#6,请参阅/sf/ask/17360801/.它是一种更清洁,现在是该语言的标准.

面向需要更优雅的解决方案来捕获一次并过滤异常的人,我使用扩展方法,如下所示.

我已经在我的库中使用了这个扩展,最初是为其他目的编写的,但它对于type检查异常非常有效.另外,imho,它看起来比一堆||语句更清晰.此外,与接受的答案不同,我更喜欢显式异常处理,因此ex is ...具有不可取的行为,因为可以将指定的类分配给父类型.

用法

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

IsAnyOf.cs扩展(请参阅Dependancies的完整错误处理示例)

namespace Common.FluentValidation
{
    public static partial class Validate
    {
        /// 
        /// Validates the passed in parameter matches at least one of the passed in comparisons.
        /// 
        /// 
        /// Parameter to validate.
        /// Values to compare against.
        /// True if a match is found.
        /// 
        public static bool IsAnyOf(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> TestActions = new List>()
            {
                (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
    {
        /// 
        /// Validates the passed in parameter matches at least one of the passed in comparisons.
        /// 
        /// 
        /// Parameter to validate.
        /// Values to compare against.
        /// True if a match is found.
        /// 
        public static bool IsAnyOf(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;
        }

        /// 
        /// Validates if any passed in parameter is equal to null.
        /// 
        /// Parameters to test for Null.
        /// True if one or more parameters are null.
        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
    {
        /// 
        /// Validates the passed in parameter is not null, throwing a detailed exception message if the test fails.
        /// 
        /// Parameter to validate.
        /// Name of tested parameter to assist with debugging.
        /// 
        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
    {
        /// 
        /// Validates the passed in parameter is not null or an empty collection, throwing a detailed exception message if the test fails.
        /// 
        /// 
        /// Parameter to validate.
        /// Name of tested parameter to assist with debugging.
        /// 
        /// 
        public static void CannotBeNullOrEmpty(this ICollection 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));
        }

        /// 
        /// Validates the passed in parameter is not null or empty, throwing a detailed exception message if the test fails.
        /// 
        /// Parameter to validate.
        /// Name of tested parameter to assist with debugging.
        /// 
        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*/
            }
        }
    }
}



19> atlaste..:

由于我觉得这些答案刚刚触及表面,我试图深入挖掘一下.

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

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

我们想要这个的原因是因为我们不希望异常处理程序捕获稍后在进程中需要的东西.当然,我们可以捕获一个例外并检查"如果"做什么,但说实话,我们并不真的想要那样做.(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 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;
    }
}

总结如下:

如果我们不想重新抛出,我们可能会考虑捕获正确的异常,并将它们存储在临时异常中.

如果处理程序很简单,并且我们想重用代码,那么最好的解决方案可能是引入一个辅助函数.

如果我们想要重新抛出,我们别无选择,只能将代码放在'Exception'catch处理程序中,这将破坏FxCop和调试器的未捕获异常.



20> Jeffrey Renn..:

这是每个C#开发人员最终面临的经典问题.

让我把你的问题分成两个问题.首先,

我可以一次捕获多个例外吗?

简而言之,没有.

这导致了下一个问题,

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

鉴于您的特定样本,后备值构建起来很便宜,我喜欢按照以下步骤操作:

    将WebId初始化为后备值.

    在临时变量中构造一个新的Guid.

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



21> HimBromBeere..:

所以你在每个异常开关中重复大量的代码?听起来像提取方法会是上帝的想法,不是吗?

所以你的代码归结为:

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


*"我想知道为什么没有人注意到代码重复."* - 呃,什么?问题*的全部要点是消除代码重复.
推荐阅读
夏晶阳--艺术
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有