【问题标题】:Catch multiple exceptions at once?一次捕获多个异常?
【发布时间】:2010-09-13 06:09:46
【问题描述】:

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

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

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

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

给定的示例相当简单,因为它只是一个GUID。但是想象一下你多次修改一个对象的代码,如果其中一个操作预期失败,你想“重置”object。但是,如果有意外的异常,我还是想抛出更高的。

【问题讨论】:

  • 如果您使用.net 4 及以上版本,我更喜欢使用聚合异常msdn.microsoft.com/en-us/library/system.aggregateexception.aspx
  • Bepenfriends- 由于 System.Guid 不会抛出 AggregateException,如果您(或某人)可以发布一个答案来展示您的方式,那就太好了会将其包装成 AggregateException 等。
  • "不鼓励简单地捕获 System.Exception。" - 如果方法可以抛出 32 种异常,那一种是什么?分别为它们中的每一个写catch?
  • 如果一个方法抛出了 32 种不同类型的异常,那就是写得不好。它要么没有捕获它自己的调用所产生的异常,要么它在一种方法中做了太多 FAR,要么这 32 个中的大多数/全部应该是带有原因码的单个异常。
  • 接受的答案已过期;而是看到这个,因为它已在顶部使用 Edit 子句进行了更新:stackoverflow.com/a/19329123/398630

标签: c# .net exception


【解决方案1】:

捕捉System.Exception并打开类型

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

【讨论】:

  • 对非 OP 编辑​​的强制性提醒:编辑更新的新答案是我们有拒绝理由的事情,超过 2k 的用户不能免除这一点。不要更新其他人的答案以反映对标准版本的更新,或适用于任何任意答案的任何技术的其他版本 - 而是发布新答案(专业提示;有更多代表适合您)。如果还有对答案的极端反对意见,您可以留下评论来解释问题并链接到现在更适用的答案。 (并根据您的喜好对答案进行投票)
  • 查看此答案的更新版本:stackoverflow.com/a/419531/1128762
【解决方案2】:

编辑:我同意其他人的说法,从 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% 的情况下,标记是完全正确的。而且,对不起,我可能弄错了,但“忽略”属性最终不会真正编译到您的应用程序中吗?

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

从现在开始的一两个月后,如果再添​​加三种异常类型,这会获得多少less 可读性? (答案:它的可读性降低了很多)。

真正的要点之一是,格式化我们每天都在查看的文本源代码的主要目的是让其他人真正、非常清楚地看到当代码运行。因为编译器将源代码变成了完全不同的东西,并且不太关心您的代码格式样式。所以多联机也很糟糕。

只是说...

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

【讨论】:

  • 您可以使用新语法:when (ex is FormatException or OverflowException or ArgumentNullException)
  • @MorganM.,我偶尔会喜欢新语法。这可能是那些时代之一。 :)
【解决方案3】:

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

这与在catch 块中使用if 语句不同,使用异常过滤器不会展开堆栈。

您可以下载Visual Studio 2015查看。

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

安装包 Microsoft.Net.Compilers

At time of writing, this will include support for C# 6.

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

【讨论】:

    【解决方案4】:

    不幸的是,在 C# 中没有,因为您需要一个异常过滤器来执行此操作,而 C# 没有公开 MSIL 的该功能。 VB.NET 确实有这个功能,例如

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

    你可以做的是使用匿名函数来封装你的 on-error 代码,然后在那些特定的 catch 块中调用它:

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

    【讨论】:

      【解决方案5】:

      异常过滤器现在在 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..? 
      }
      

      【讨论】:

      • 这种方法是首选,不仅因为它简单明了,而且不满足条件时不必展开堆栈,与rethrow相比提供了更好的性能和诊断信息。跨度>
      【解决方案6】:

      为了完整起见,从.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 的解构时,对这个无用答案的下一个无用更新出现了。:)

      【讨论】:

        【解决方案7】:

        如果您可以将您的应用程序升级到 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 中唯一无法在早期版本中模拟的新功能。首先,重新投掷比跳过接球意味着更多的开销。其次,它在语义上不等价。当您调试代码时,新功能会完整地保留堆栈。如果没有此功能,故障转储就没有多大用处,甚至毫无用处。

        查看discussion about this on CodePlex不再可用。还有一个example showing the difference

        【讨论】:

        • Throw without exception 保留堆栈,但“throw ex”会覆盖它。
        【解决方案8】:

        使用 C# 7 the answer from Michael Stum 可以在保持 switch 语句的可读性的同时得到改进:

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

        感谢Orace 的评论,这可以通过省略丢弃变量使用 C# 8 来简化:

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

        并以 C# 8 作为 switch 表达式:

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

        正如Nechemia Hoffmann 指出的那样。后一个示例将导致堆栈跟踪丢失。这可以通过使用Jürgen Steinblock 描述的扩展方法在抛出前捕获堆栈跟踪来防止:

        catch (Exception ex)
        {
            WebId = ex switch
            {
                _ when ex is FormatException || ex is OverflowException => Guid.Empty,
                _ => throw ex.Capture()
            };
        }
        
        public static Exception Capture(this Exception ex)
        {
            ExceptionDispatchInfo.Capture(ex).Throw();
            return ex;
        }
        

        这两种样式都可以通过 C# 9 的模式匹配增强功能进行简化:

        catch (Exception ex)
        {
            switch (ex)
            {
                case FormatException or OverflowException:
                    WebId = Guid.Empty;
                    break;
                default:
                    throw;
            }
        } 
        
        catch (Exception ex)
        {
            WebId = ex switch
            {
                _ when ex is FormatException or OverflowException => Guid.Empty,
                _ => throw ex.Capture()
            };
        }
        

        【讨论】:

        • 如果你throw ex,你会不会丢失堆栈跟踪?
        • 是的,在 switch 表达式示例(第二个示例)中,您确实丢失了堆栈跟踪。感谢您指出了这一点。 (要明确一点:在第一个示例中您不会丢失它)
        • 对于第一个代码块,_ 在 C#8 中不再需要
        • @Orace:感谢您的评论。我相应地更新了答案。
        【解决方案9】:

        如果您不想在 catch 范围内使用 if 语句,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;
        }
        

        仅当 InvalidDataExceptionArgumentNullException 时,此代码才会捕获 Exception

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

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

        请注意,与 catch 范围内的 if 语句相反,Exception Filters 不能抛出 Exceptions,当它们抛出时,或者当条件不是 true 时,下一个 @987654341将改为评估 @ 条件:

        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 会相应地执行):

        另外,特别是 GuidGuid.TryParse 方法。

        【讨论】:

          【解决方案10】:

          C# 9 更新

          使用 C# 9 中的new pattern matching enhancements,您可以缩短异常过滤器中的表达式。现在,捕获多个异常很简单:

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

          【讨论】:

            【解决方案11】:
            catch (Exception ex) when (ex is FormatException or OverflowException)
            {
                WebId = Guid.Empty;
            }
            

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

            【讨论】:

              【解决方案12】:

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

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

              CA1800: Do not cast unnecessarily 说“考虑测试 'as' 运算符的结果”,但如果这样做,您将编写比单独捕获每个异常更多的代码。

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

              bool exThrown = false;
              
              try
              {
                  // Something
              }
              catch (FormatException) {
                  exThrown = true;
              }
              catch (OverflowException) {
                  exThrown = true;
              }
              
              if (exThrown)
              {
                  // Something else
              }
              

              【讨论】:

                【解决方案13】:

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

                【讨论】:

                  【解决方案14】:

                  这是马特的答案的变体(我觉得这样更干净一些)...使用方法:

                  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
                  }
                  

                  【讨论】:

                    【解决方案15】:

                    Joseph Daigle's Answer 是一个很好的解决方案,但我发现下面的结构更整洁,更不容易出错。

                    catch(Exception ex)
                    {   
                        if (!(ex is SomeException || ex is OtherException)) throw;
                    
                        // Handle exception
                    }
                    

                    反转表达式有几个优点:

                    • 不需要返回语句
                    • 代码没有嵌套
                    • 不会有忘记 Joseph 解决方案中与表达式分离的“throw”或“return”语句的风险。

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

                    catch(Exception ex) { if (!(ex is SomeException || ex is OtherException)) throw;
                    
                        // Handle exception
                    }
                    

                    编辑: C# 6.0 中的exception filtering 将使语法更简洁,并且在任何当前解决方案上都带有number of other benefits。 (最值得注意的是让堆栈安然无恙)

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

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

                    【讨论】:

                      【解决方案16】:

                      @迈克尔

                      您的代码稍作修改:

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

                      字符串比较丑陋而缓慢。

                      【讨论】:

                        【解决方案17】:

                        怎么样

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

                        【讨论】:

                        • 只有当 Catch-Code 可以完全移动到 Try-Block 时才有效。但是,您对一个对象进行多次操作的成像代码,其中一个操作失败,并且您想要“重置”该对象。
                        • 在这种情况下,我会添加一个重置函数并从多个 catch 块中调用它。
                        • OP 已请求一次捕获多个异常。您可以在不同的块中捕获它们
                        【解决方案18】:

                        注意和警告:Yet another kind, functional style.

                        链接中的内容不能直接回答您的问题,但将其扩展为如下所示很简单:

                        static void Main() 
                        { 
                            Action body = () => { ...your code... };
                        
                            body.Catch<InvalidOperationException>() 
                                .Catch<BadCodeException>() 
                                .Catch<AnotherException>(ex => { ...handler... })(); 
                        }
                        

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

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

                        【讨论】:

                        • 您的链接今天返回 404 错误页面。
                        • 不幸的是,我不记得太多了,但我会将答案留给任何可以根据我发布的想法继续工作的人。不是很困难(或今天非常有用:))
                        【解决方案19】:

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

                        针对希望more elegant solution 捕获一次并过滤异常的人,我使用如下所示的扩展方法。

                        我的库中已经有了这个扩展,最初是为其他目的而编写的,但它非常适合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*/
                                    }
                                }
                            }
                        }
                        

                        【讨论】:

                        • 增强语言不是“更优雅”。在许多地方,这实际上造成了维护地狱。多年后,许多程序员并不为他们创造了什么样的怪物而自豪。这不是您习惯阅读的内容。它可能会引起“嗯?”效果,甚至是严重的“WTF”。有时,这很令人困惑。它所做的唯一一件事就是让那些需要在维护后期处理它的人更难掌握代码——只是因为一个程序员试图变得“聪明”。多年来,我了解到那些“聪明”的解决方案很少也是好的解决方案。
                        【解决方案20】:

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

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

                        // 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 中实现的方式是“过滤器”,它在 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.");
                        

                        另一个解决方案是捕获异常对象并相应地处理它。根据上面的上下文,最直接的翻译是这样的:

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

                        【讨论】:

                          【解决方案21】:

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

                          让我把你的问题分成 2 个问题。第一个,

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

                          简而言之,没有。

                          这会引出下一个问题,

                          鉴于我无法在同一个 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!
                          【解决方案22】:

                          请注意,我确实找到了一种方法,但这看起来更像是 The Daily WTF 的材料:

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

                          【讨论】:

                          • -1 票,+5 WTF :-) 这不应该被标记为答案,但它很有趣。
                          • 不管我们做得多简单。但他也没有坐视不理,拿出自己的观点来解决。真的很感激。
                          • 实际上不要这样做,在 C# 6 或任何其他答案中使用异常过滤器 - 我把它放在这里特别是“这是一种方法,但它很糟糕,我想做点什么更好”。
                          • 为什么这样不好?我很困惑你不能直接在 switch 语句中使用异常。
                          • @MKesper 我看到它不好的几个原因。它需要将完全限定的类名编写为字符串文字,这很容易受到编译器无法挽救的拼写错误的影响。 (这很重要,因为在许多商店中,错误案例没有经过充分测试,因此它们中的小错误更有可能被遗漏。)它也将无法匹配一个 子类 的异常的具体情况。而且,由于是字符串,这些案例将被 VS 的“查找所有引用”之类的工具遗漏 - 如果您想在捕获到特定异常的任何地方添加清理步骤。
                          【解决方案23】:

                          所以您在每个异常开关中都重复了大量代码?听起来提取一个方法是个好主意,不是吗?

                          所以你的代码归结为:

                          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 开始,您还拥有其他人已经提到的 exception-filters。所以你可以把上面的代码修改成这样:

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

                          【讨论】:

                          • “我想知道为什么没有人注意到代码重复。” - 呃,什么? 问题的重点是消除代码重复。
                          【解决方案24】:

                          想在这个已经很长的帖子中添加我的简短回答。没有提到的是 catch 语句的优先顺序,更具体地说,您需要了解您尝试捕获的每种异常类型的范围。

                          例如,如果您使用“catch-all”异常作为 Exception,它将先于所有其他 catch 语句,您显然会遇到编译器错误,但是如果您颠倒顺序,您可以链接您的 catch声明(我认为有点反模式)你可以把所有的 Exception 类型放在底部,这将捕获任何在你的尝试中不适合更高层的异常。捕获块:

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

                          Exception Hierarchy

                          【讨论】:

                            【解决方案25】:

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

                            例如:

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

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

                            【讨论】:

                              【解决方案26】:

                              这里值得一提。您可以响应多种组合(异常错误和异常消息)。

                              我在尝试将控件对象投射到数据网格中时遇到了一个用例场景,其内容为 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
                              } 
                              

                              【讨论】:

                                【解决方案27】:

                                我想建议最短的答案(另一种功能风格):

                                        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。

                                【讨论】:

                                  【解决方案28】:
                                             try
                                             {
                                                  WebId = new Guid(queryString["web"]);
                                             }
                                             catch (Exception ex)
                                             {
                                                  string ExpTyp = ex.GetType().Name;
                                                  if (ExpTyp == "FormatException")
                                                  {
                                                       WebId = Guid.Empty;
                                                  }
                                                  else if (ExpTyp == "OverflowException")
                                                  {
                                                       WebId = Guid.Empty;
                                                  }
                                             }
                                  

                                  【讨论】:

                                    【解决方案29】:

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

                                    【讨论】:

                                    • 这个例子没有展示任何异常过滤器的使用。
                                    • 这是在 c#6.0 中过滤异常的标准方法
                                    • 再次看看异常过滤器到底是什么。您的示例中没有使用异常过滤器。 this answer 有一个恰当的例子,比你早一年发布。
                                    • 异常过滤的一个例子是catch (HttpException e) when e.GetHttpCode() == 400 { WriteLine("Bad Request"; }
                                    猜你喜欢
                                    • 1970-01-01
                                    • 2020-05-10
                                    • 1970-01-01
                                    • 2013-05-21
                                    • 1970-01-01
                                    • 2019-09-25
                                    • 2010-10-03
                                    • 1970-01-01
                                    相关资源
                                    最近更新 更多