提问



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


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


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


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


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

最佳参考


抓住System.Exception并打开类型


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

    throw;
}

其它参考1


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


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


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


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


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


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


当你从现在开始一个月或两个月再添加三个异常类型时,这会得到多少 less 可读性? (答案:它的 很多 可读性较差)。


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


只是说......


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

其它参考2


正如其他人所指出的那样,你可以在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来检查这一点。[108]


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



  安装包Microsoft.Net.Compilers



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



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


其它参考3


不幸的是,不是在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


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


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


如果格式错误,TryParse永远不会抛出异常并返回false,将WebId设置为Guid.Empty[110]





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


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


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


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


并像这样使用它们:


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





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

其它参考5


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


请参阅CodePlex上有关此问题的讨论。一个显示差异的例子。[111] [112]

其它参考6


如果您不想在catch范围内使用if语句,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;
}


只有在InvalidDataExceptionArgumentNullException时,此代码才会捕获Exception


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


static int a = 8;

...

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


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


static int a = 7;

static int b = 0;

...

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



  输出:一般捕获。



如果有多true Exception Filter - 第一个将被接受:


static int a = 8;

static int b = 4;

...

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



  输出:捕获。



正如你在MSIL中看到的那样,代码没有翻译成if语句,而是FiltersExceptions不能从标有[[Filters的区域内抛出。 93]]和Filter 2但抛出Exception的过滤器将失败,也就是在endfilter命令确定过滤器成功/失败之前推送到堆栈的最后一个比较值( Catch 1 XOR Catch 2会相应执行):


[113]


此外,具体Guid具有Guid.TryParse方法。 [114]

其它参考7


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


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


CA1800:不要不必要地投射说考虑测试作为操作符的结果,但是如果你这样做,那么你将编写的代码比单独捕获每个异常的代码要多。 [116]


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


bool exThrown = false;

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

if (exThrown)
{
    // Something else
}

其它参考8


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


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
}

其它参考9


@Micheal


您的代码略有修改版本:


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


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

其它参考10


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

其它参考11


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


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中的异常过滤将使语法更清晰,并且与当前任何解决方案相比还具有许多其他优点。 (最值得注意的是让堆栈毫发无伤)[118]


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


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

其它参考12


怎么样


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

其它参考13


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



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


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

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


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


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

其它参考14


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

其它参考15


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


面向需要更优雅的解决方案以捕获一次并过滤异常的人,我使用扩展方法,如下所示。


我已经在我的库中使用了这个扩展,最初是为其他目的编写的,但它对于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*/
            }
        }
    }
}

其它参考16


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


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

其它参考17


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


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


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


我们想要这个的原因是因为我们不希望异常处理程序捕获我们稍后需要的东西。当然,我们可以捕获一个Exception并检查if该做什么,但让我们成为老实说,我们真的不想那样。(FxCop,调试器问题,丑陋)


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


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


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


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


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


为了使它在C#中工作,我们需要一个具有正确异常基类型的临时变量。为了控制代码的流程,我们可以添加一些分支。开始:


    Exception ex;
    try
    {
        throw new ArgumentException(); // for demo purposes; won't be caught.
        goto noCatch;
    }
    catch (ArgumentOutOfRangeException e) {
        ex = e;
    }
    catch (IndexOutOfRangeException e) {
        ex = e;
    }

    Console.WriteLine("Handle the exception 'ex' here :-)");
    // throw ex ?

noCatch:
    Console.WriteLine("We're done with the exception handling.");


显而易见的缺点是我们不能正确地重新投掷,而且 - 老实说 - 这是一个非常丑陋的解决方案。通过执行分支消除可以稍微修复丑陋,这使解决方案略微更好:


Exception ex = null;
try
{
    throw new ArgumentException();
}
catch (ArgumentOutOfRangeException e)
{
    ex = e;
}
catch (IndexOutOfRangeException e)
{
    ex = e;
}
if (ex != null)
{
    Console.WriteLine("Handle the exception here :-)");
}


这只留下了重新投掷。为了实现这一点,我们需要能够在catch块内执行处理 - 并且使这项工作的唯一方法是捕获异常对象。


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


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


总结如下:



  • 如果我们不想重新投掷,我们可能会考虑捕获正确的例外情况,并将它们暂时存储。

  • 如果处理程序很简单,并且我们想重新使用代码,那么最好的解决方案可能是引入辅助函数。

  • 如果我们想要重新抛出,我们别无选择,只能将代码放入Exceptioncatch处理程序中,这会破坏FxCop和调试器的未被捕获的异常。


其它参考18


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


所以你的代码归结为:


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

其它参考19


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


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


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


总之,没有。


这导致了下一个问题,


如果我不能在同一个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);
}

其它参考20


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


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

其它参考21


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


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


            try
            {
                // do some work here
            }
            catch (WebException ex)
            {
                // catch a web excpetion
            }
            catch (ArgumentException ex)
            {
                // do some stuff
            }
            catch (Exception ex)
            {
                // you should really surface your errors but this is for example only
                throw new Exception("An error occurred: " + ex.Message);
            }


我强烈建议人们查看这个MSDN文档:


例外层次结构[126]

其它参考22


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


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

其它参考23


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


例如。:


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

// ...

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


我是怎么做到的,试图找到 simple是美丽的模式

其它参考24


只需调用try并捕获两次。


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


就这么简单!

其它参考25


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