一次捕获多个异常?

Catch multiple exceptions at once?

提问人:Michael Stum 提问时间:9/26/2008 最后编辑:Grigory ZhadkoMichael Stum 更新时间:11/12/2023 访问量:823215

问:

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

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

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

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

给定的示例相当简单,因为它只是一个 GUID。但是想象一下,在代码中,您多次修改一个对象,如果其中一个操作意外失败,则要“重置”.但是,如果出现意外异常,我仍然想把它扔得更高。object

C# .NET 异常

评论

8赞 Albert Arul prakash 10/18/2013
如果您使用的是 .net 4 及更高版本,我更喜欢使用 aggregateexception msdn.microsoft.com/en-us/library/system.aggregateexception.aspx
2赞 weir 1/31/2014
Bepenfriends- 由于 System.Guid 不会抛出 AggregateException,因此如果您(或某人)可以发布一个答案,说明如何将其包装到 AggregateException 等中,那就太好了。
28赞 Giorgi Moniava 5/24/2015
“不鼓励简单地捕获 System.Exception。” - 如果方法可以抛出 32 种类型的异常,那该怎么办?分别为他们每个人写 catch?
13赞 Flynn1179 5/19/2017
如果一个方法抛出 32 种不同类型的异常,那么它写得很糟糕。它要么没有捕获它自己的调用正在发生的异常,要么它在一种方法中做了太多的事情,或者这 32 个中的大多数/全部都应该是一个带有原因代码的异常。
2赞 BrainSlugs83 5/18/2019
接受的答案已过时;请看这个,因为它已更新为顶部的 Edit 子句: stackoverflow.com/a/19329123/398630

答:

9赞 3 revs, 3 users 61%Michael Stum #1

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

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

评论

13赞 Aaron 9/26/2008
-1 票, +5 WTF :-)这不应该被标记为答案,但它是荒谬的。
3赞 Maxymus 1/21/2016
不管我们能做得多么简单。但他并没有坐以待毙,并提出了解决它的观点。真的很感激。
2赞 Michael Stum 1/22/2016
不过,实际上不要这样做,请使用 C# 6 中的异常筛选器或任何其他答案 - 我在这里专门将其作为“这是一种方式,但它很糟糕,我想做得更好”。
0赞 MKesper 6/8/2016
为什么这很糟糕?我很困惑你不能直接在switch语句中使用异常。
4赞 Mark Amery 10/24/2017
@MKesper我看到它不好的几个原因。它需要将完全限定的类名编写为字符串文字,这容易受到编译器无法避免的拼写错误的影响。(这很重要,因为在许多商店中,错误案例的测试不太好,因此其中的微不足道的错误更有可能被遗漏。它还将无法匹配异常,该异常是指定情况之一的子类。而且,由于是字符串,VS 的“查找所有引用”等工具会遗漏这些情况 - 如果您想在捕获特定异常的任何地方添加清理步骤,这是相关的。
13赞 Maurice 9/26/2008 #2

怎么样

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

评论

1赞 Michael Stum 9/26/2008
只有当 Catch-Code 可以完全移动到 Try-Block 中时,这才有效。但是,对一个对象进行多次操作,中间的一个操作失败,并且想要“重置”该对象的映像代码。
4赞 Maurice 9/26/2008
在这种情况下,我将添加一个重置函数并从多个 catch 块调用它。
0赞 avivgood2 9/25/2020
OP 已请求一次捕获多个异常。你在不同的街区捕捉它们
2444赞 Joseph Daigle 9/26/2008 #3

捕获并打开类型System.Exception

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

评论

30赞 Zoe is on strike 7/7/2021
必须提醒那些不是OP的编辑者:在更新的新答案中进行编辑是我们有拒绝理由的,>2k用户也不能免于此。不要更新其他人的答案以反映对标准版本的更新,或适用于任何任意答案的任何技术的其他版本 - 而是发布一个新答案(专业提示;这对你来说有更多的代表)。如果对答案还有极端的反对意见,您可以发表评论解释问题并链接到现在更适用的任何答案。(并随心所欲地对答案进行投票)
4赞 Johan 10/5/2022
在我看来,一个稍微干净的语法(从 .net 5/C# 9 开始):if(例如是 FormatException 或 OverflowException)
0赞 Paul Nakitare 1/6/2023
考虑而不仅仅是维护 StackTrace。stackoverflow.com/a/57123173/9610801ExceptionDispatchInfo.Capture(ex).Throw();throw;
16赞 FlySwat 9/26/2008 #4

@Micheal

代码的略微修改版本:

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

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

206赞 Greg Beech 9/26/2008 #5

不幸的是,在 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();
}
44赞 Konstantin Spirin 1/7/2009 #6
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;
}
21赞 Matt 7/31/2010 #7

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

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

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

无论如何,我会这样做:

bool exThrown = false;

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

if (exThrown)
{
    // Something else
}
18赞 bsara 9/1/2012 #8

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

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

    WebId = Guid.Empty;
}

将抛出任何其他异常,并且不会命中代码。如果您不希望其他异常导致程序崩溃,只需在其他两个捕获之后添加此内容:WebId = Guid.Empty;

...
catch (Exception)
{
     // something, if anything
     return; // only need this if you follow the example I gave and put it all in a method
}
149赞 Athari 4/13/2013 #9

为了完整起见,从 .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 中实现输出参数的解构时。:)

12赞 nawfal 5/18/2013 #10

告诫和警告:另一种功能性风格。

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

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

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

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

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

评论

0赞 Peter 9/5/2021
您的链接今天返回 404 错误页面。
0赞 nawfal 9/5/2021
不幸的是,我不记得太多了,但我会把答案留给任何可以从我发布的想法中工作的人。不是很困难(或者今天非常有用:))
861赞 Craig Tullis 10/12/2013 #11

编辑:我同意其他人的观点,即从 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;

但这就是我的观点。你们都听说过函数/方法,对吧?认真地。编写一个通用函数,然后从每个 catch 块中调用它。ErrorHandler

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

对于任何可能对编程相对陌生的人来说,维护阶段将占项目整个生命周期的 98.7% 或更多,而进行维护的可怜笨蛋几乎肯定会是你以外的人。他们很有可能会把50%的时间花在骂你的名字上。

当然,FxCop 会对你咆哮,所以你还必须在代码中添加一个属性,该属性与正在运行的程序精确相关,并且只是为了告诉 FxCop 忽略一个问题,在 99.9% 的情况下,它在标记方面是完全正确的。而且,对不起,我可能弄错了,但是“忽略”属性最终不是真的编译到您的应用程序中了吗?

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

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

实际上,其中一个要点是,格式化我们每天都在看的文本源代码的大部分意义在于让其他人真正、非常清楚地看到代码运行时实际发生的事情。因为编译器将源代码变成了完全不同的东西,并且不关心您的代码格式样式。所以全线也很糟糕。

只是说...

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

评论

14赞 Morgan M. 9/20/2021
您可以使用新语法:when (ex is FormatException or OverflowException or ArgumentNullException)
10赞 HodlDwon 11/28/2013 #12

2015-12-15 更新:请参阅 C#6 的 https://stackoverflow.com/a/22864936/1718702。它更干净,现在是语言的标准。

为了满足那些想要一个更优雅的解决方案来捕获一次并过滤异常的人,我使用了一个扩展方法,如下所示。

我的库中已经有这个扩展,最初是为其他目的而写的,但它非常适合检查异常。另外,恕我直言,它看起来比一堆陈述更干净。此外,与公认的答案不同,我更喜欢显式异常处理,因此具有不希望的行为,因为派生类可以分配给父类型)。type||ex is ...

用法

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

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

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

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

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

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

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

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

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

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

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

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

                    Console.ReadKey();
                }
            }
        }
    }
}

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

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

            // Return no matches found
            return false;
        }

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

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

            return false;
        }
    }
}

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

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

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

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

两个示例 NUnit 单元测试

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

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

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

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

                //else throw original
                throw;
            }
        }

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

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

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

评论

1赞 Kaii 10/10/2014
增强语言并不是“更优雅”。在许多地方,这实际上造成了维护地狱。多年后,许多程序员并不为自己创造的怪物感到自豪。这不是你习惯阅读的。它可能会引起“嗯”效应,甚至是严重的“WTF”。有时令人困惑。它唯一做的就是让那些需要在以后维护中处理它的人更难掌握代码——只是因为一个程序员试图变得“聪明”。多年来,我了解到那些“聪明”的解决方案很少是好的解决方案。
498赞 Joe 4/4/2014 #13

正如其他人所指出的,您可以在 catch 块中加入一个语句来确定正在发生的事情。C#6 支持异常筛选器,因此以下操作将起作用:if

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)
{
    …
}

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

可以下载 Visual Studio 2015 来查看此内容。

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

安装包 Microsoft.Net.编译器

在撰写本文时,这将包括对 C# 6 的支持。

引用此包将导致使用 包含在 软件包,而不是任何系统安装的版本。

评论

0赞 Enrico 4/7/2022
您无法执行泛型异常逻辑,因为您无法命名捕获相同变量名称的两个异常。
9赞 atlaste 10/21/2014 #14

由于我觉得这些答案只是触及表面,所以我试图更深入地挖掘。

因此,我们真正想做的是一些不编译的东西,比如说:

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

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

那么,为什么这段代码无法编译 - 我们如何才能以这样一种方式对其进行破解呢?

如果我们看一下代码,我们真正想做的是转接呼叫。但是,根据 MS 分区 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”基类型的临时变量。为了控制代码的流程,我们可以添加一些分支。这里是:

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

总而言之:

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

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

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

    // Handle exception
}

反转表达式有几个优点:

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

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

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

    // Handle exception
}

编辑:C# 6.0 中的异常筛选将使语法更简洁一些,并且与任何当前解决方案相比,它还具有许多其他好处。(最值得注意的是,堆栈不受伤害)

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

catch(Exception ex) when (ex is SomeException || ex is OtherException)
{
    // Handle exception
}
91赞 Maniero 4/1/2015 #16

如果可以将应用程序升级到 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;
    }
    else {
        throw;
    }
}

但事实并非如此。实际上,这是 C# 6 中唯一在以前的版本中无法模拟的新功能。首先,重新投掷意味着比跳过接球更多的开销。其次,它在语义上不等同。在调试代码时,新功能会保持堆栈不变。如果没有此功能,故障转储就不太有用,甚至毫无用处。

CodePlex 上查看有关此内容的讨论不再可用。还有一个例子显示了差异

评论

12赞 Ivan 4/12/2017
Throw 无一例外地保留堆栈,但“throw ex”将覆盖它。
0赞 Alsty 11/14/2022
你忘了吗?第二个示例将抛出异常,即使它是 或returnFormatExceptionOverflowException
0赞 0xF 11/24/2023
他忘记了.我编辑了答案。else
-26赞 Kashif 5/20/2015 #17

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

评论

14赞 user247702 5/27/2015
此示例未显示异常筛选器的任何用法。
0赞 Kashif 5/27/2015
这是在 c#6.0 中过滤异常的标准方法
5赞 user247702 5/27/2015
再看看异常过滤器到底是什么。示例中未使用异常筛选器。在你一年前发布的这个答案中有一个适当的例子。
6赞 saluce 11/14/2015
异常筛选的一个示例是catch (HttpException e) when e.GetHttpCode() == 400 { WriteLine("Bad Request"; }
19赞 SHM 10/4/2015 #18

在 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");
 }
41赞 Tamir Vered 10/8/2015 #19

如果不想在作用域内使用语句,则可以在 C# 6.0 中使用异常筛选器语法,该语法在预览版本中已受 CLR 支持,但仅存在于 / 中:ifcatchVB.NETMSIL

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

此代码将捕获仅当它是 或 .ExceptionInvalidDataExceptionArgumentNullException

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

static int a = 8;

...

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

请注意,与 作用域内的语句相反,不能抛出 ,当它们抛出时,或者当条件不是时,将改为计算下一个条件:ifcatchException FiltersExceptionstruecatch

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

输出:一般捕获。

当有多个 - 第一个将被接受:trueException 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");
}

输出:Catch。

正如您在代码中所看到的,代码不是转换为语句,而是转换为 ,并且不能从标有 和 的区域内抛出,但抛出的过滤器将失败,此外,命令之前推送到堆栈的最后一个比较值将确定过滤器的成功/失败(XOR 将相应地执行):MSILifFiltersExceptionsFilter 1Filter 2ExceptionendfilterCatch 1Catch 2

Exception Filters MSIL

此外,具体而言,Guid 具有 Guid.TryParse 方法。

6赞 MakePeaceGreatAgain 9/22/2016 #20

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

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

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

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

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

从 C#6 开始,您还可以获得其他人已经提到的异常过滤器。因此,您可以将上面的代码修改为:

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

评论

4赞 Mark Amery 10/24/2017
“我想知道为什么没有人注意到这种代码重复。- 呃,什么?问题的全部意义在于消除代码重复。
5赞 Trevor 4/16/2017 #21

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

例如,如果您使用“catch-all”异常作为 Exception,它将在所有其他 catch 语句之前,并且您显然会得到编译器错误,但是,如果您颠倒顺序,您可以链接您的 catch 语句(我认为有点反模式),您可以将 catch-all Exception 类型放在底部,这将捕获任何不符合您尝试中更高级别的异常。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文档:

异常层次结构

8赞 Jeffrey Rennie 11/16/2017 #22

这是每个 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);
}

评论

0赞 Trevor 3/5/2018
这是一个很好的“生态编码”,即你正在提前考虑你的代码和数据足迹,并确保没有泄漏一半的处理值。很高兴遵循这种模式,谢谢杰弗里!
59赞 Fabian 1/5/2018 #23

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

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

多亏了 Orace 注释,这可以通过省略 discard 变量来简化 C# 8:

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

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

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

正如 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()
    };
}

评论

0赞 Nechemia Hoffmann 1/7/2021
如果你,你不会失去堆栈跟踪吗?throw ex
0赞 Fabian 1/8/2021
是的,在开关表达式示例(第二个示例)中,您确实松动了堆栈跟踪。感谢您指出这一点。(需要明确的是:在第一个示例中,您不会丢失它)
0赞 Orace 5/27/2021
对于第一个代码块,在 C#8 中也不再需要_
4赞 Żubrówka 1/23/2018 #24

也许尝试保持代码简单,例如将通用代码放在方法中,就像在代码的任何其他部分中所做的那样,这些部分不在 catch 子句中?

例如:

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

// ...

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

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

168赞 Mat J 7/11/2018 #25

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

评论

3赞 joe 7/29/2019
这种方法之所以受欢迎,不仅是因为它简单明了,而且在不满足条件时不必展开堆栈,与重新抛出相比,它提供了更好的性能和诊断信息。
1赞 George 12/7/2018 #26

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

当尝试在数据网格中强制转换控件对象时,我遇到了一个用例场景,内容为 TextBox、TextBlock 或 CheckBox。在本例中,返回的 Exception 是相同的,但消息各不相同。

try
{
 //do something
}
catch (Exception ex) when (ex.Message.Equals("the_error_message1_here"))
{
//do whatever you like
} 
catch (Exception ex) when (ex.Message.Equals("the_error_message2_here"))
{
//do whatever you like
} 

评论

0赞 Tommaso Ercole 4/1/2022
信息可以翻译成各种文化。中继异常类型和/或错误代码(如果可用)会好得多
0赞 Evgeny Gorbovoy 5/27/2019 #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 方法,但在参数中使用 returns 和 Func,而不是 Action。

72赞 Nechemia Hoffmann 1/7/2021 #28

C# 9 更新

使用 C# 9 中新增的模式匹配增强功能,可以缩短异常筛选器中的表达式。现在,捕获多个异常很简单:

try
{
    WebId = new Guid(queryString["web"]);
}
catch (Exception e) when (e is FormatException or OverflowException)
{
    WebId = Guid.Empty;
}
-6赞 Saleem Kalro 11/21/2021 #29
           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;
                }
           }

评论

2赞 Gert Arnold 4/29/2022
这不是检查类型的好方法。
1赞 Dercsár 1/5/2023
首先:它不会消除原始问题的重复代码