首页 文章

一次捕获多个异常?

提问于
浏览
1800

不鼓励简单地 grab System.Exception . 相反,只应捕获"known"例外 .

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

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

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

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

27 回答

  • 3

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

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

    只需调用try并捕获两次 .

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

    就这么简单!

  • 10

    警告和警告:又一种功能性风格 .

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

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

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

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

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

    怎么样

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

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

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

    CA1800: Do not cast unnecessarily对"consider testing the result of the 'as' operator instead"说,但如果你这样做,你将编写的代码多于单独捕获每个异常的代码 .

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

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

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

    public void TryCatch(...)
    {
        try
        {
           // something
           return;
        }
        catch (FormatException) {}
        catch (OverflowException) {}
    
        WebId = Guid.Empty;
    }
    

    将抛出任何其他异常并且代码 WebId = Guid.Empty; 赢得't be hit. If you don' t想要其他异常来使程序崩溃,只需在其他两个捕获之后添加:

    ...
    catch (Exception)
    {
         // something, if anything
         return; // only need this if you follow the example I gave and put it all in a method
    }
    
  • 266

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

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

    不幸的是,不是在C#中,因为你需要一个例外过滤器来做这件事而C#不公开MSIL的那个功能 . VB.NET确实具有这种能力,例如

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

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

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

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

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

    反转表达式有一些优点:

    • 不需要退货声明

    • 代码未嵌套

    • 有's no risk of forgetting the ' throw ' or '返回' statements that in Joseph'的解决方案与表达式分开 .

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

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

    Edit: C#6.0中的exception filtering将使语法更清晰,并且在任何当前解决方案中都带有number of other benefits . (最值得注意的是让堆栈不受伤害)

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

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

    @Micheal

    您的代码略有修改版本:

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

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

  • 27

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

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

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

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

    // 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'中)?我们希望编译器处理这种情况的方式是注意两个异常的公共基类型是'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和调试器的未捕获异常 .

  • 18

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

    例如,如果你使用"catch-all"异常作为 Exception 它将在所有其他catch语句之前,你显然会遇到编译器错误,但是如果你颠倒顺序你可以链接你的catch语句(我认为反模式的位)你可以把底部的catch-all Exception 类型,这将捕获任何在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文档:

    Exception Hierarchy

  • 126

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

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

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

    Usage

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

    IsAnyOf.cs Extension (See Full Error Handling Example for Dependancies)

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

    Full Error Handling Example (Copy-Paste to new Console app)

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

    Two Sample NUnit Unit Tests

    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*/
                }
            }
        }
    }
    
  • 12

    值得一提的是这里 . 您可以响应多个组合(异常错误和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
    }
    
  • 29

    grab System.Exception 并打开类型

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

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

    如果您可以将您的应用程序升级到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中唯一不能在先前版本中模拟的新功能 . 首先,重新投掷意味着比跳过捕获更多的开销 . 其次,它在语义上不相同 . 在调试代码时,新功能可以保持堆栈完好无损 . 如果没有此功能,崩溃转储就不那么有用甚至无用了 .

    discussion about this on CodePlex . 并且example showing the difference .

  • 4

    如果您不想在 catch 范围内使用 if 语句, in C# 6.0 you can use Exception Filters syntax 已在预览版本中受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 时,将评估下一个 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 语句,但是 FiltersExceptions 不能从标有 Filter 1Filter 2 的区域内抛出,但抛出 Exception 的过滤器将失败,也是最后一个比较值在 endfilter 命令确定过滤器成功/失败之前推送到堆栈( Catch 1 XOR Catch 2 将相应地执行):

    另外,具体 Guid 具有Guid.TryParse方法 .

  • 6

    也许尝试保持代码简单,例如将公共代码放在方法中,就像在代码中不在catch子句中的任何其他部分一样?

    例如 . :

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

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

  • 0

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

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

    Can I catch multiple exceptions at once?

    总之,没有 .

    这导致了下一个问题,

    How do I avoid writing duplicate code given that I can't catch multiple exception types in the same catch() block?

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

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

    正如其他人指出的那样,你可以在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 语句不同,使用异常过滤器 will not 展开堆栈 .

    你可以下载Visual Studio 2015进行检查 .

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

    安装 - 包装Microsoft.Net.Compilers

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

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

  • 19

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

  • -20

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

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

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

    ORIGINAL:

    我知道我在这里参加聚会有点晚了,但圣烟......

    直接追逐,这种复制更早的答案,但如果你真的想要为几种异常类型执行一个共同的操作,并保持整个事情在一个方法的范围内整洁,为什么不只是使用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; 的三个相同实例 .

    但那个's sort of my point. Y'都听说过功能/方法,对吗?认真 . 编写一个通用的 ErrorHandler 函数,就像从每个catch块中调用它一样 .

    如果你问我,第二个例子(带有 ifis 关键字)的可读性要低得多,同时在项目的维护阶段也容易出错 .

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

    当然FxCop对你咆哮,所以你必须在你的代码中添加一个属性,该属性与正在运行的程序具有精确的zip,并且只是告诉FxCop忽略一个问题,在99.9%的情况下它完全是标记正确 . 而且,抱歉,我可能会弄错,但是"ignore"属性最终是否真的被编译到您的应用程序中?

    将整个 if 测试打开一行让它更具可读性?我不这么认为 . 我的意思是,我确实让另一位程序员在很久以前激烈地争辩说,将更多的代码放在一条线上会使它变得更加坚定 . 但是他当然是疯狂的疯狂 . 试图向他解释(直言不讳 - 这很有挑战性)解释器或编译器如何将这条长线分开为离散的单指令每行语句 - 如果他继续前进,则基本上与结果相同只是使代码可读而不是试图超越编译器 - 对他没有任何影响 . 但我离题了 .

    当你再添加三个例外类型时,从现在开始一个月或两个月,这会带来多少可读性? (答案:它的可读性低于_266809) .

    实际上,其中一个重点是,我们每天都在查看文本源代码的大部分格式是让其他人真正,非常明显地在代码运行时实际发生了什么 . 因为编译器将源代码转换为完全不同的东西,并且对代码格式化风格无关紧要 . 所以一对一的线路也很糟糕 .

    只是说......

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

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

    所以你的代码归结为:

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

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

相关问题