关于C:一次捕获多个异常?

Catch multiple exceptions at once?

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

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

1
2
3
4
5
6
7
8
9
10
11
12
try
{
    WebId = new Guid(queryString["web"]);
}
catch (FormatException)
{
    WebId = Guid.Empty;
}
catch (OverflowException)
{
    WebId = Guid.Empty;
}

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

给出的例子相当简单,因为它只是一个GUID。但是想象一下代码,您在其中多次修改一个对象,如果其中一个操作以预期的方式失败,那么您希望"重置"object。但是,如果有一个意外的异常,我仍然想把它扔得更高。


捕获System.Exception并打开类型

1
2
3
4
5
6
7
8
9
10
catch (Exception ex)            
{                
    if (ex is FormatException || ex is OverflowException)
    {
        WebId = Guid.Empty;
        return;
    }

    throw;
}


编辑:我说的是谁的人认为,与concur,C滤波器的# 6.0,现在在perfectly异常精细的方式去:catch (Exception ex) when (ex is ... || ex is ... )

除了我仍然恨的一种长的地图布局,个人的代码在下面的出样。我认为这是它的审美功能,提高阅读能力,因为我相信它。一些可能disagree:

1
2
3
4
5
catch (Exception ex) when (
    ex is ...
    || ex is ...
    || ex is ...
)

原文:

我知道我在这里的小晚的派对,但神圣的烟幕。

直切的追逐,这一类duplicates早期的答案,但如果你真的想完成一些常见的异常类型和行动保持整洁和整洁的所有的东西的范围内的一个方法,为什么不只是使用内联函数λ/ /关闭到下面的什么样?我认为,机会是很好的意思,你只是想要实现你端上,使封闭的单独的方法,你可以使用所有的地方。然后它将是超级容易,没有实际的代码的其余部分在结构上的变化。好吗?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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 ); }
}

我不能帮助,但奇迹(警告:超前小sarcasm反讽/)为什么在线地球去到所有这一切的努力,只是基本上是以下:

1
2
3
4
5
6
7
try
{
    // try some stuff
}
catch( FormatException ex ){}
catch( OverflowException ex ){}
catch( ArgumentNullException ex ){}

这一变化...with一些疯狂的例子代码的气味,我均,仅仅假装是你节省几个键击。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// sorta sucks, let's be honest...
try
{
    // try some stuff
}
catch( Exception ex )
{
    if (ex is FormatException ||
        ex is OverflowException ||
        ex is ArgumentNullException)
    {
        // write to a log, whatever...
        return;
    }
    throw;
}

因为它更可读的肯定是不是自动的。

我认为,在相同的实例(左三/* write to a log, whatever... */ return;走出的第一个例子。

但这是我点的排序。你们有听过/方法的功能,对吗?(一)严重。写的函数和类,共ErrorHandler,呼叫可以从每个catch块。

如果你问我,第二个实例(与if和关键字都不is)是显着的和可读的同时,显着更多的错误倾向,在维护阶段的项目。

任何人谁是在维护阶段,可能是一个相对较新的方案,将包括进入98.7 %或以上的寿命,你的整个项目,和穷人的笨蛋做的几乎是肯定要维护的是你比其他人。有一个很好的机会与他们的,他们会花50%的时间在职诅咒你的名字。

当然,在与你和你barks FxCop也要添加属性到您的代码已经运行到与准确的压缩程序,和仅仅是一个有问题的忽视告诉FxCop是一例,它是在99.9%)在完全正确的标记。对不起,我可能是,但不是这样的mistaken,端上"忽略"属性为真的编译你的应用程序?

会把整个一个在线测试在线if使它更可读。我不认为如此。我是一个程序员的均值,这是一间长8 vehemently认为,更多的代码会做一个在线实时运行的更快。但当然,他是赤裸裸的疯狂的坚果。他试图解释到,这与直面对具有挑战性的是如何解释器或编译器)将打破这一成离散的长线分开的指令语句的每个地图--基本上相同的结果,如果他有走到前面,只是使代码的可读性而不是试图聪明的编译器. -他没有对whatso永远。但我digress。

这有多少不可读的程序,当你添加更多的异常类型的三个月,从现在或双吗?(答案:它有很多不可读)。

主要的一点,那是真的,大部分的点源代码的文本格式的,我们的每一天都看的是使它真的,真的很明显的对其他的人类,其实是什么发生当代码运行。因为编译器的源代码转成完全不同的东西和不在乎关于你的无格式的代码风格。一个操作系统的全网络地图全烂了。

只是说……

1
2
3
4
5
6
7
8
9
10
// super sucks...
catch( Exception ex )
{
    if ( ex is FormatException || ex is OverflowException || ex is ArgumentNullException )
    {
        // write to a log, whatever...
        return;
    }
    throw;
}


正如其他人所指出的,您可以在catch块中使用if语句来确定发生了什么。C 6支持异常过滤器,因此以下内容可以工作:

1
2
3
4
5
try {}
catch (Exception e) when (MyFilter(e))
{
    …
}

那么,MyFilter方法可以看起来像这样:

1
2
3
4
private bool MyFilter(Exception e)
{
  return e is ArgumentNullException || e is FormatException;
}

或者,可以全部以内联方式完成(when语句的右侧必须是布尔表达式)。

1
2
3
4
5
try {}
catch (Exception e) when (e is ArgumentNullException || e is FormatException)
{
    …
}

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

您可以下载Visual Studio 2015来查看。

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

Install-Package Microsoft.Net.Compilers

在写作时,这将包括对C 6的支持。

Referencing this package will cause the project to be built using the
specific version of the C# and Visual Basic compilers contained in the
package, as opposed to any system installed version.


不幸的是,不在C中,因为您需要一个异常过滤器来完成它,C不公开MSIL的功能。但是,vb.net确实具有这种功能,例如

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

您可以使用匿名函数来封装出错代码,然后在这些特定的catch块中调用它:

1
2
3
4
5
6
7
8
9
10
11
12
13
Action onError = () => WebId = Guid.Empty;
try
{
    // something
}
catch (FormatException)
{
    onError();
}
catch (OverflowException)
{
    onError();
}


为了完整起见,由于.NET 4.0,代码可以重写为:

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

台盼从不抛出异常,如果格式错误,则返回false,将webid设置为Guid.Empty

由于C 7,您可以避免在单独的行中引入变量:

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

您还可以创建用于分析返回的元组的方法,这些元组在.NET框架中从4.6版开始还不可用:

1
2
(bool success, Guid result) TryParseGuid(string input) =>
    (Guid.TryParse(input, out Guid result), result);

像这样使用它们:

1
2
3
4
WebId = TryParseGuid(queryString["web"]).result;
// or
var tuple = TryParseGuid(queryString["web"]);
WebId = tuple.success ? tuple.result : DefaultWebId;

当在C 12中实现out参数的解构时,对这个无用答案的下一个无用更新就会出现。:)


如果你能把你的应用升级到C 6,你就很幸运了。新的C版本实现了异常过滤器。所以你可以这样写:

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

有些人认为此代码与

1
2
3
4
5
6
catch (Exception ex) {                
    if (ex is FormatException || ex is OverflowException) {
        WebId = Guid.Empty;
    }
    throw;
}

但事实并非如此。实际上,这是C 6中唯一一个在以前的版本中无法模拟的新功能。首先,重新抛出意味着比跳过捕获更多的开销。第二,它在语义上不是等价的。在调试代码时,新特性会保持堆栈的完整性。如果没有这个特性,崩溃转储就不那么有用,甚至是无用的。

请参阅关于CodePlex的讨论。以及一个显示差异的例子。


异常过滤器现在在C 6+中可用。你可以做到

1
2
3
4
5
6
7
8
try
{
       WebId = new Guid(queryString["web"]);
}
catch (Exception ex) when(ex is FormatException || ex is OverflowException)
{
     WebId = Guid.Empty;
}

如果您不想在catch范围内使用if语句,那么在C# 6.0范围内,您可以使用Exception Filters语法,该语法在预览版本中已经得到clr的支持,但只存在于VB.NETMSIL中:

1
2
3
4
5
6
7
8
try
{
    WebId = new Guid(queryString["web"]);
}
catch (Exception exception) when (exception is FormatException || ex is OverflowException)
{
    WebId = Guid.Empty;
}

只有当它是InvalidDataExceptionArgumentNullException时,此代码才会捕获Exception

实际上,你基本上可以在when条款中加入任何条件:

1
2
3
4
5
6
7
8
static int a = 8;

...

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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");
}

Output: General catch.

当有多个trueException Filter时,第一个将被接受:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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");
}

Output: Catch.

正如你在MSIL中看到的,代码不是转换成if语句,而是转换成Filters语句,并且Exceptions不能从Filter 1Filter 2标记的区域中抛出,但是抛出Exception的过滤器将失败,最后一个比较值也推送到了edoc之前的堆栈中。x1〔25〕命令将决定过滤器的成功/失败(Catch 1xor Catch 2将相应地执行):

Exception Filters MSIL

另外,具体来说,Guid具有Guid.TryParse方法。


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

1
2
3
4
5
6
7
8
9
10
11
12
catch (Exception ex)
{
    switch (ex)
    {
        case FormatException _:
        case OverflowException _:
            WebId = Guid.Empty;
            break;
        default:
            throw;
    }
}


接受的答案似乎是可以接受的,除了codeanalysis/fxcop会抱怨它捕获了一般的异常类型。

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

CA1800:不要不必要地强制转换,而是说"考虑测试‘as’操作符的结果",但是如果这样做,您将编写比单独捕获每个异常更多的代码。

不管怎样,我要做的是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
bool exThrown = false;

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

if (exThrown)
{
    // Something else
}


在C 6中,建议的方法是使用异常过滤器,下面是一个示例:

1
2
3
4
5
6
7
8
9
 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");
 }

这是马特答案的一个变种(我觉得这有点干净)…使用一种方法:

1
2
3
4
5
6
7
8
9
10
11
12
public void TryCatch(...)
{
    try
    {
       // something
       return;
    }
    catch (FormatException) {}
    catch (OverflowException) {}

    WebId = Guid.Empty;
}

任何其他异常都将被抛出,代码WebId = Guid.Empty;将不会被命中。如果不希望其他异常使程序崩溃,只需在其他两个捕获之后添加:

1
2
3
4
5
6
...
catch (Exception)
{
     // something, if anything
     return; // only need this if you follow the example I gave and put it all in a method
}


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

1
2
3
4
5
6
catch(Exception ex)
{  
    if (!(ex is SomeException || ex is OtherException)) throw;

    // Handle exception
}

反转表达式有几个优点:

  • 不需要返回语句
  • 代码没有嵌套
  • 不存在忘记"抛出"或"返回"语句的风险,在约瑟夫的解决方案中,这些语句与表达式分离。

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

1
2
3
4
catch(Exception ex) { if (!(ex is SomeException || ex is OtherException)) throw;

    // Handle exception
}

编辑:C 6.0中的异常过滤将使语法更加清晰,并且比任何当前解决方案都有许多其他好处。(最显著的是保持堆栈未损坏)

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

1
2
3
4
catch(Exception ex) when (ex is SomeException || ex is OtherException)
{
    // Handle exception
}


@米夏埃尔.沃尔什

稍微修改过的代码版本:

1
2
3
4
5
6
7
8
9
10
11
catch (Exception ex)
{
   Type exType = ex.GetType();
   if (exType == typeof(System.FormatException) ||
       exType == typeof(System.OverflowException)
   {
       WebId = Guid.Empty;
   } else {
      throw;
   }
}

字符串比较既丑陋又缓慢。


怎么样

1
2
3
4
5
6
7
8
9
10
11
try
{
    WebId = Guid.Empty;
    WebId = new Guid(queryString["web"]);
}
catch (FormatException)
{
}
catch (OverflowException)
{
}


警告和警告:还有另一种,功能性的风格。

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

1
2
3
4
5
6
7
8
static void Main()
{
    Action body = () => { ...your code... };

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

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

更大的问题是为什么。我不认为成本大于收益。)


1
2
3
4
5
6
7
8
9
10
catch (Exception ex)
{
    if (!(
        ex is FormatException ||
        ex is OverflowException))
    {
        throw;
    }
    Console.WriteLine("Hello");
}

更新2015-12-15:参见https://stackoverflow.com/a/22864936/1718702了解C_6。它是一种干净的语言,现在是标准的语言。

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

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

用法

1
2
3
4
5
6
7
8
if (ex.GetType().IsAnyOf(
    typeof(FormatException),
    typeof(ArgumentException)))
{
    // Handle
}
else
    throw;

isanyof.cs扩展(有关依赖项,请参阅完整的错误处理示例)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
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;
        }
    }
}

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
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.

Parameter_Name:"
+ p_name, default(Exception));

            if (p_parameter.Count <= 0)
                throw new ArgumentOutOfRangeException("Collection cannot be empty.

Parameter_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.

Parameter_Name:"
+ p_name, default(Exception));
        }
    }
}

两个样本nunit单元测试

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
using System;
using System.Collections.Generic;
using Common.FluentValidation;
using NUnit.Framework;

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

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

                //else throw original
                throw;
            }
        }

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

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

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


因为我觉得这些答案只是表面上的,所以我试图再深入一点。

所以我们真正想做的是一些不编译的事情,比如:

1
2
3
4
5
6
7
8
9
10
11
12
// Won't compile... damn
public static void Main()
{
    try
    {
        throw new ArgumentOutOfRangeException();
    }
    catch (ArgumentOutOfRangeException)
    catch (IndexOutOfRangeException)
    {
        // ... handle
    }

之所以需要这样做,是因为我们不希望异常处理程序捕获稍后在流程中需要的内容。当然,我们可以捕捉到一个异常,并检查一下"如果"该怎么做,但老实说,我们并不真的想要这样做。(fxcop,调试器问题,丑陋)

那么,为什么这段代码不能编译呢?我们怎么能以这样的方式破解它呢?

如果我们看一下代码,我们真正想做的是转发呼叫。但是,根据MS分区II,IL异常处理程序块不会这样工作,在本例中这是有意义的,因为这意味着"exception"对象可以有不同的类型。

或者用代码编写,我们要求编译器做类似的事情(好吧,这并不完全正确,但这是我猜最接近的事情):

1
2
3
4
5
6
7
8
9
10
11
12
// Won't compile... damn
try
{
    throw new ArgumentOutOfRangeException();
}
catch (ArgumentOutOfRangeException e) {
    goto theOtherHandler;
}
catch (IndexOutOfRangeException e) {
theOtherHandler:
    Console.WriteLine("Handle!");
}

这不会编译的原因很明显:"$exception"对象的类型和值是什么(存储在变量"e"中)?我们希望编译器处理这一点的方法是注意,两个异常的公共基类型都是"exception",对于一个变量,使用它来包含这两个异常,然后只处理捕获的两个异常。在IL中实现的方法是"filter",它在vb.net中可用。

为了使它在C中工作,我们需要一个具有正确"exception"基类型的临时变量。为了控制代码流,我们可以添加一些分支。下面是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
    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.");

这样做的明显缺点是我们不能正确地扔球,而且-好吧,老实说-这是一个非常丑陋的解决方案。通过执行分支消除,可以稍微解决问题,这使得解决方案稍微更好:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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"对象。

此时,我们可以添加一个单独的函数,使用重载解析来处理不同类型的异常,或者处理异常。两者都有缺点。首先,下面是使用helper函数执行此操作的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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.");

另一种解决方案是捕获异常对象并相应地处理它。基于以上上下文,最直译的是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
try
{
    throw new ArgumentException();
}
catch (Exception e)
{
    Exception ex = (Exception)(e as ArgumentException) ?? (e as IndexOutOfRangeException);
    if (ex != null)
    {
        Console.WriteLine("Handle the exception here :-)");
        // throw ?
    }
    else
    {
        throw;
    }
}

因此得出结论:

  • 如果我们不想重新抛出,我们可以考虑捕获正确的异常,并将它们存储在临时的异常中。
  • 如果处理程序很简单,并且我们希望重用代码,那么最好的解决方案可能是引入一个助手函数。
  • 如果我们想重新抛出代码,我们别无选择,只能将代码放入"exception"捕获处理程序中,这将破坏fxcop和调试器未捕获的异常。


所以你在每个异常切换中重复了很多代码?听起来提取方法是上帝的主意,不是吗?

所以你的代码可以归结为:

1
2
3
4
5
6
7
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中,您还有其他人已经提到的异常过滤器。因此,您可以将上面的代码修改为:

1
2
3
4
5
try { ... }
catch(Exception e) when(e is Exception1 || e is Exception2)
{
    Reset(instance);
}


这是每个开发人员最终面临的一个经典问题。

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

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

简而言之,没有。

这就引出了下一个问题,

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

考虑到您的具体示例,其中回撤值的构建成本较低,我希望遵循以下步骤:

  • 将WebID初始化为回退值。
  • 在临时变量中构造新的GUID。
  • 将webid设置为完全构造的临时变量。使其成为try块的最终语句。
  • 所以代码看起来像:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    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。

    如果构建回退值很昂贵,并且重置值更便宜,那么我会将重置代码移动到它自己的函数中:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    try
    {
        WebId = new Guid(queryString["web"]);
        // More initialization code goes here.
    }
    catch (FormatException) {
        Reset(WebId);
    }
    catch (OverflowException) {
        Reset(WebId);
    }


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

    例如,如果您使用"catch all"异常作为异常,它将处理所有其他catch语句,并且您显然会得到编译器错误。但是,如果您颠倒了可以链接catch语句的顺序(我认为这是一个反模式),您可以将catch all异常类型放在底部,这将捕获任何异常tha在你的尝试中没有迎合更高的要求。接住障碍:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
                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文档:

    异常层次结构


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

    例如。:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    try
    {
        // ...
    }
    catch (FormatException)
    {
        DoSomething();
    }
    catch (OverflowException)
    {
        DoSomething();
    }

    // ...

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

    我就是这么做的,试着找到简单漂亮的图案


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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    catch (Exception ex)
    {
        switch (ex.GetType().Name)
        {
            case"System.FormatException":
            case"System.OverflowException":
                WebId = Guid.Empty;
                break;
            default:
                throw;
        }
    }


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

    我在试图在数据报中强制转换控制对象时遇到了一个用例场景,内容可以是文本框、文本块或复选框。在这种情况下,返回的异常是相同的,但消息不同。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    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
    }

    打两次"试抓"。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    try
    {
        WebId = new Guid(queryString["web"]);
    }
    catch (FormatException)
    {
        WebId = Guid.Empty;
    }
    try
    {
        WebId = new Guid(queryString["web"]);
    }
    catch (OverflowException)
    {
        WebId = Guid.Empty;
    }

    就这么简单!!


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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    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");
        }
    }