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

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

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

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

给定的示例相当简单,因为它只是一个GUID。但是想象一下,在代码中,您多次修改一个对象,如果其中一个操作预期失败,您希望“重置”该对象。然而,如果有意外的异常,我仍然想把它推得更高。


注意,我确实找到了一种方法,但这看起来更像是《每日WTF》的素材:

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

怎么样

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

捕获System.Exception并打开类型

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

@密歇根州

代码的略微修订版本:

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

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


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

catch (Exception ex) when (ex is FormatException or OverflowException)
{
    WebId = Guid.Empty;
}

or

catch (Exception ex)
{
    if (ex is not FormatException and not OverflowException)
        throw;

    WebId = Guid.Empty;
}

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

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

CA1800:不要进行不必要的强制转换。“请考虑测试‘as’运算符的结果”,但如果这样做,您将编写比单独捕获每个异常更多的代码。

无论如何,我会这样做:

bool exThrown = false;

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

if (exThrown)
{
    // Something else
}

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

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
}

为了完整起见,自.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参数的解构时,这个无用答案的下一个无用更新就来了


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

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

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

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

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

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


编辑:我确实同意其他人的观点,即从C#6.0开始,异常过滤器现在是一个非常好的方法:在(ex是…||ex是…)时捕获(exception ex)

除了我仍然有点讨厌一行长的布局,我个人会像下面这样布局代码。我认为这既实用又美观,因为我相信它能提高理解力。有些人可能不同意:

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

因为它当然不会自动变得更可读。

当然,我留下了/*写入日志的三个相同实例,不管怎样…*/回来在第一个例子中。

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

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

对于任何可能对编程相对陌生的人来说,维护阶段将占项目整个生命周期的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;
}

更新2015-12-15:参见https://stackoverflow.com/a/22864936/1718702对于C#6。这是一种更干净的语言,现在是标准的语言。

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

我的库中已经有了这个扩展,它最初是为了其他目的而编写的,但它对于异常的类型检查非常有效。另外,我的意思是,它看起来比一堆||语句更干净。此外,与公认的答案不同,我更喜欢显式异常处理,因此ex是。。。具有不期望的行为,因为派生类可分配给父类型)。

用法

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单元测试示例

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

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

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

try { … }
catch (Exception e) when (MyFilter(e))
{
    …
}

MyFilter方法可能看起来像这样:

private bool MyFilter(Exception e)
{
  return e is ArgumentNullException || e is FormatException;
}

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

try { … }
catch (Exception e) when (e is ArgumentNullException || e is FormatException)
{
    …
}

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

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

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

安装程序包Microsoft.Net.Compilers

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

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


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

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

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

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

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

如果我们查看代码,我们真正想做的是转发调用。然而,根据MS Partition II,IL异常处理程序块不会像这样工作,这在本例中是有意义的,因为这意味着“异常”对象可以具有不同的类型。

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

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

这不会编译的原因很明显:“$exception”对象具有什么类型和值(此处存储在变量“e”中)?我们希望编译器处理这一点的方式是注意,这两个异常的公共基类型都是“异常”,将其用于包含这两个例外的变量,然后仅处理捕获的两个例外。在IL中实现这一点的方式是“filter”,它在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和调试器未捕获的异常。


约瑟夫·戴格尔的答案是一个很好的解决方案,但我发现下面的结构更整洁,更不容易出错。

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

    // Handle exception
}

反转表达式有几个优点:

return语句不是必需的代码未嵌套不存在忘记“抛出”或“返回”语句的风险,这些语句在Joseph的解决方案中与表达式分离。

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

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
}

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

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

有些人认为此代码与

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

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

请在CodePlexNo available上查看有关此的讨论。以及一个显示差异的示例。


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

try
{
    DoSomeHttpRequest();
}
catch (System.Web.HttpException e)
{
    switch (e.GetHttpCode())
    {
        case 400:
            WriteLine("Bad Request");
        case 500:
            WriteLine("Internal Server Error");
        default:
            WriteLine("Generic Error");
    }
}

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

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

如果您不想在catch范围内使用If语句,在C#6.0中,您可以使用CLR在预览版本中已经支持但仅存在于VB.NET/MSIL中的异常过滤器语法:

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

只有当异常为InvalidDataException或ArgumentNullException时,此代码才会捕获该异常。

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

static int a = 8;

...

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

请注意,与catch范围内的if语句不同,异常过滤器不能抛出异常,当抛出异常时,或者当条件不为真时,将计算下一个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");
}

输出:常规捕获。

当存在多个真正的异常筛选器时,将接受第一个:

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语句,而是转换为筛选器,并且异常不能从标记为筛选器1和筛选器2的区域中抛出,但是抛出异常的筛选器将失败,在endfilter命令之前推送到堆栈的最后一个比较值也将决定过滤器的成功/失败(Catch 1 XOR Catch 2将相应地执行):

此外,特别是Guid具有Guid.TryParse方法。


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

因此,您的代码归结为:

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

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

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

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

异常层次结构


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

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

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

简而言之,没有。

这引出了下一个问题,

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

给定您的特定示例,其中回退值构建起来很便宜,我喜欢遵循以下步骤:

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

使用C#7,Michael Stum的答案可以得到改进,同时保持switch语句的可读性:

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

由于Orace注释,这可以通过省略丢弃变量而用C#8来简化:

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

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

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

正如内切米娅·霍夫曼指出的那样。后一个示例将导致堆栈丢失。这可以通过使用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()
    };
}

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

例如。:

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

// ...

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

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


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

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

当我试图在数据网格中转换控件对象时,我遇到了一个用例场景,其中的内容为TextBox、TextBlock或CheckBox。在这种情况下,返回的异常相同,但消息不同。

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

我想建议最简短的答案(一种更实用的方式):

        Catch<FormatException, OverflowException>(() =>
            {
                WebId = new Guid(queryString["web"]);
            },
            exception =>
            {
                WebId = Guid.Empty;
            });

为此,您需要创建几个“Catch”方法重载,类似于System.Action:

    [DebuggerNonUserCode]
    public static void Catch<TException1, TException2>(Action tryBlock,
        Action<Exception> catchBlock)
    {
        CatchMany(tryBlock, catchBlock, typeof(TException1), typeof(TException2));
    }

    [DebuggerNonUserCode]
    public static void Catch<TException1, TException2, TException3>(Action tryBlock,
        Action<Exception> catchBlock)
    {
        CatchMany(tryBlock, catchBlock, typeof(TException1), typeof(TException2), typeof(TException3));
    }

你想多少就多少。但是你需要做一次,你可以在所有项目中使用它(或者,如果你创建了一个nuget包,我们也可以使用它)。

以及CatchMany实施:

    [DebuggerNonUserCode]
    public static void CatchMany(Action tryBlock, Action<Exception> catchBlock,
        params Type[] exceptionTypes)
    {
        try
        {
            tryBlock();
        }
        catch (Exception exception)
        {
            if (exceptionTypes.Contains(exception.GetType())) catchBlock(exception);
            else throw;
        }
    }

p.s.为了代码的简单性,我没有进行空检查,请考虑添加参数验证。

第2页如果要从catch返回值,则必须执行相同的catch方法,但在参数中使用return和Func而不是Action。


C#9的更新

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

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

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