一次捕获多个异常?

时间:2008-09-25 20:56:09

标签: c# .net exception exception-handling

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

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

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

我想知道:有没有办法捕获这两个例外,只调用一次WebId = Guid.Empty电话?

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

29 个答案:

答案 0 :(得分:1941)

抓住System.Exception并启用类型

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

    throw;
}

答案 1 :(得分:460)

编辑:我同意其他人的意见,从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;的三个相同实例。

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

如果您问我,第二个示例(使用ifis关键字)的可读性明显降低,同时在项目维护阶段也更容易出错。

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

当然,FxCop会咆哮你,所以你必须 为你的代码添加一个属性,该属性具有与正在运行的程序完全相同的zip,并且只是那里告诉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;
}

答案 2 :(得分:302)

正如其他人所指出的那样,你可以在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)
{
    …
}

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

您可以下载Visual Studio 2015进行检查。

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

  

安装 - 打包Microsoft.Net.Compilers

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

  

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

答案 3 :(得分:185)

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

答案 4 :(得分:129)

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

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

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


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

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

您还可以创建用于解析返回元组的方法,这些方法自版本4.6起在.NET Framework中不可用:

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

并像这样使用它们:

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

当在C#12中实现out-parameters的解构时,接下来对这个无用的答案进行无用的更新:)

答案 5 :(得分:71)

如果您可以将您的应用程序升级到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

答案 6 :(得分:57)

异常过滤器现在在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 )
{
   //do something with members of ae, say ae.InnerExceptions
}

答案 7 :(得分:31)

如果您不想在if范围内使用catch语句,C# 6.0中的 ,您可以使用Exception Filters语法 在预览版本中已受CLR支持,但仅存在于VB.NET / MSIL中:

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

此代码只有在ExceptionInvalidDataException时才会抓住ArgumentNullException

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

static int a = 8;

...

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

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

static int a = 7;

static int b = 0;

...

try
{
    throw new InvalidDataException();
}
catch (Exception exception) when (exception is InvalidDataException && a / b == 2)
{
    Console.WriteLine("Catch");
}
catch (Exception exception) when (exception is InvalidDataException || exception is ArgumentException)
{
    Console.WriteLine("General catch");
}
  

输出:一般捕获。

如果有多个true Exception Filter,则会接受第一个:

static int a = 8;

static int b = 4;

...

try
{
    throw new InvalidDataException();
}
catch (Exception exception) when (exception is InvalidDataException && a / b == 2)
{
    Console.WriteLine("Catch");
}
catch (Exception exception) when (exception is InvalidDataException || exception is ArgumentException)
{
    Console.WriteLine("General catch");
}
  

输出:Catch。

正如您在MSIL中看到的那样,代码未转换为if语句,而是转移到Filters,并且Exceptions无法从标记的区域内抛出使用Filter 1Filter 2但是抛出Exception的过滤器将失败,而endfilter命令将确定成功/失败之前,最后一个比较值也会被推送到堆栈过滤器(Catch 1 XOR Catch 2将相应执行):

Exception Filters MSIL

此外,具体Guid采用Guid.TryParse方法。

答案 8 :(得分:21)

C# 9 更新

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

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

答案 9 :(得分:18)

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

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

答案 10 :(得分:18)

接受的答案似乎可以接受,但CodeAnalysis / FxCop会抱怨它正在捕捉一般异常类型。

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

CA1800: Do not cast unnecessarily 表示“考虑测试'as'运算符的结果”,但如果你这样做,那么你将编写的代码多于捕获每个异常的代码分开。

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

bool exThrown = false;

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

if (exThrown)
{
    // Something else
}

答案 11 :(得分: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");
 }

答案 12 :(得分:17)

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

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

    // Handle exception
}

反转表达式有一些优点:

  • 不需要退货声明
  • 代码未嵌套
  • 不存在忘记约瑟夫解决方案中与表达式分离的“抛出”或“返回”语句的风险。

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

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

    // Handle exception
}

修改 C#6.0中的exception filtering将使语法更清晰,并且在任何当前解决方案上都带有number of other benefits。 (最值得注意的是离开堆栈)

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

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

答案 13 :(得分:17)

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

答案 14 :(得分:16)

@Micheal

您的代码略有修订版本:

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

字符串比较是丑陋而缓慢的。

答案 15 :(得分:13)

怎么样

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

答案 16 :(得分:11)

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

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

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

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

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

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

答案 17 :(得分:11)

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

答案 18 :(得分:9)

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

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

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

<强>用法

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

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

namespace Common.FluentValidation
{
    public static partial class Validate
    {
        /// <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*/
            }
        }
    }
}

答案 19 :(得分:7)

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

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

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

我们想要这个的原因是因为我们不希望异常处理程序捕获我们稍后需要的东西。当然,我们可以查看例外情况,并查看“如果&#39;怎么做,但说实话,我们并不是真的想要那样。 (FxCop,调试器问题,丑陋)

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

如果我们查看代码,我们真正想做的就是转发呼叫。然而,根据MS Partition II,IL异常处理程序块不会像这样工作,在这种情况下是有意义的,因为这意味着“异常”#39;对象可以有不同的类型。

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

// Won't compile... damn
try
{
    throw new ArgumentOutOfRangeException();
}
catch (ArgumentOutOfRangeException e) {
    goto theOtherHandler;
}
catch (IndexOutOfRangeException e) {
theOtherHandler:
    Console.WriteLine("Handle!");
}

这不会编译的原因很明显:&#39; $例外&#39;是什么类型和价值?对象有(这里存储在变量&#39; e&#39;)?我们希望编译器处理这种情况的方式是要注意两个异常的公共基类型是&#39;异常&#39;,使用它来使变量包含两个异常,然后只处理捕获的两个异常。在IL中实现它的方式是&#39; filter&#39;,它可以在VB.Net中获得。

为了使它在C#中运行,我们需要一个具有正确&#39;异常&#39;的临时变量。基础类型。为了控制代码的流程,我们可以添加一些分支。这是:

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

只留下“重新抛出”。为了实现这一目标,我们需要能够在“捕获”中执行处理。阻止 - 实现这项工作的唯一方法是抓住“异常”#39;宾语。

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

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

总结如下:

  • 如果我们不想重新投掷,我们可能会考虑捕获正确的例外情况,并将其存储在临时状态。
  • 如果处理程序很简单,并且我们想重新使用代码,那么最好的解决方案可能就是引入一个辅助函数。
  • 如果我们想重新投掷,我们别无选择,只能将代码放入“例外”中。 catch处理程序,它将破坏FxCop和你的调试器未被捕获的异常。

答案 20 :(得分:6)

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

所以你的代码归结为:

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

答案 21 :(得分:6)

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

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

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

简而言之,没有。

这导致了下一个问题,

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

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

  1. 将WebId初始化为后备值。
  2. 在临时变量中构造新的Guid。
  3. 将WebId设置为完全构造的临时变量。将此作为try {}块的最终声明。
  4. 所以代码如下:

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

答案 22 :(得分:4)

希望在我已经很长的帖子中添加我的简短答案。没有提到的是catch语句的优先顺序,更具体地说,你需要知道你试图捕获的每种类型的异常的范围。

例如,如果你使用“catch-all”异常作为 Exception ,它将在所有其他catch语句之前,你显然会遇到编译器错误,但是如果你颠倒顺序,你可以链接你的catch语句(我认为反模式的一点)你可以在底部放置全部异常类型,这将捕获任何在你的尝试中不能满足更高级别的异常。 catch block:

            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

答案 23 :(得分:4)

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

E.g:

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

// ...

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

我是怎么做到的,试图找到 simple is beautiful 模式

答案 24 :(得分:1)

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

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

答案 25 :(得分:0)

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

答案 26 :(得分:0)

我想提出最简短的答案(一种功能样式):

        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。为了简化代码,我没有进行空检查,考虑添加参数验证。

p.s.2 如果要从catch中返回一个值,则必须执行相同的Catch方法,但要使用return和Func而不是Action作为参数。

答案 27 :(得分:-13)

只需调用try并捕获两次。

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

只是简单!!

答案 28 :(得分:-22)

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