一次捕获多个异常?

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

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

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

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

给定的例子相当简单,因为它只是一个#0。但是想象一下代码,你多次修改一个对象,如果其中一次操作意外失败,你想“重置”object。然而,如果有意外的异常,我仍然想扔得更高。

771772 次浏览

请注意,我确实找到了一种方法来做到这一点,但这看起来更像是每日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;}

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}

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

public void TryCatch(...){try{// somethingreturn;}catch (FormatException) {}catch (OverflowException) {}
WebId = Guid.Empty;}

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

...catch (Exception){// something, if anythingreturn; // 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;// orvar tuple = TryParseGuid(queryString["web"]);WebId = tuple.success ? tuple.result : DefaultWebId;

这个无用答案的下一个无用更新是在C#12中实现外参数的解构时。:)

警告和警告:另一种类型,功能风格。

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

static void Main(){Action body = () => { ...your code... };
body.Catch<InvalidOperationException>().Catch<BadCodeException>().Catch<AnotherException>(ex => { ...handler... })();}

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

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

编辑:我同意其他人的说法,从C#6.0开始,异常过滤器现在是一个非常好的方法:catch (Exception ex) when (ex is ... || ex is ... )

除了我仍然有点讨厌单行布局,并会亲自将代码布局如下。我认为这既实用又美观,因为我相信它可以提高理解能力。有些人可能不同意:

catch (Exception ex) when (ex is ...|| ex is ...|| ex is ...)

原件:

我知道我参加派对有点晚了但是我的天…

开门见山,这种重复了之前的答案,但是如果你真的想对几种异常类型执行一个共同的操作,并在一个方法的范围内保持整齐,为什么不直接使用lambda/闭包/内联函数来做如下事情呢?我的意思是,很有可能你最终会意识到,你只是想让那个闭包成为一个单独的方法,你可以在任何地方使用。但是这样做会非常容易,而不需要在结构上改变其余的代码。对吧?

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%或更多,而做维护的那个可怜的傻瓜几乎肯定不是你。他们很有可能会把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;}

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

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

我的库中已经有了这个扩展,最初是为了其他目的而编写的,但它非常适合type检查异常。另外,依我看,它看起来比一堆||语句更干净。此外,与公认的答案不同,我更喜欢显式异常处理,因此ex is ...有不受欢迎的行为,因为derrided类可分配给父类型)。

用法

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

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){// Validatep_parameter.CannotBeNull("p_parameter");p_comparisons.CannotBeNullOrEmpty("p_comparisons");
// Test for any matchforeach (var item in p_comparisons)if (p_parameter.Equals(item))return true;
// Return no matches foundreturn 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(){// InitList<Action<string>> TestActions = new List<Action<string>>(){(key) => { throw new FormatException(); },(key) => { throw new ArgumentException(); },(key) => { throw new KeyNotFoundException();},(key) => { throw new OutOfMemoryException(); },};
// Runforeach (var FooAction in TestActions){// Mid-Level Error Handler (Appends Data for Log)try{// Initvar SomeKeyPassedToFoo = "FooParam";
// Low-Level Handler (Handle/Log and Keep going)try{FooAction(SomeKeyPassedToFoo);}catch (Exception ex){if (ex.GetType().IsAnyOf(typeof(FormatException),typeof(ArgumentException))){// HandleConsole.WriteLine("ex was {0}", ex.GetType().Name);Console.ReadKey();}else{// Add some Debug infoex.Data.Add("SomeKeyPassedToFoo", SomeKeyPassedToFoo.ToString());throw;}}}catch (KeyNotFoundException ex){// Handle differentlyConsole.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){// Validatep_parameter.CannotBeNull("p_parameter");p_comparisons.CannotBeNullOrEmpty("p_comparisons");
// Test for any matchforeach (var item in p_comparisons)if (p_parameter.Equals(item))return true;
// Return no matches foundreturn 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)thrownewArgumentNullException(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 Exceptionsreturn;}
//else throw originalthrow;}}
[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*/}}}}

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

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

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

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

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

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

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

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

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

安装软件包Microsoft. Net.编译器

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

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

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

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

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

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

那么为什么这段代码不能编译——我们如何才能以这种方式破解它呢?

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

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

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

这不能编译的原因很明显:“$异常”对象有什么类型和值(这里存储在变量“e”中)?我们希望编译器处理这个的方式是注意两个异常的公共基类型是“Exception”,对一个变量使用它来包含两个异常,然后只处理被捕获的两个异常。这在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;}}

所以总结一下:

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

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中的异常过滤将使语法更加清晰,并且在任何当前解决方案中都带有多项其他福利。(最值得注意的是,堆栈没有受到伤害)

以下是使用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中唯一无法在以前版本中模拟的新功能。首先,重新抛出意味着比跳过捕获更多的开销。其次,它在语义上不等同。新功能在调试代码时保留了堆栈完好无损。没有这个功能,崩溃转储就不那么有用甚至无用了。

参见CodePlex上关于这个的示例显示差异

在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 OverflowExConsole.WriteLine("E");}

如果您不想在catch范围内使用if语句,在#2中,您可以使用#3语法在预览版本中已经被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,当它们抛出时,或者当条件不是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");}

输出:一般捕获。

当有更多然后一个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。

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

异常过滤器MSIL

此外,具体地说,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-all例外类型放在底部,这将捕获任何在你的try… cat块中没有满足更高要求的异常:

            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 onlythrow new Exception("An error occurred: " + ex.Message);}

我强烈建议大家查看此MSDN文档:

异常层次结构

这是每个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永远不会设置为半构造值,并且仍然是Guide.空。

如果构建后备值很昂贵,而重置值要便宜得多,那么我会将重置代码移动到它自己的函数中:

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ürgenSteinblock描述的扩展方法在抛出之前捕获堆栈跟踪来防止:

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..?}

这里值得一提的是,您可以响应多个组合(异常错误和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包,我们也可以使用它)。

和Catch许多实现:

    [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方法,但在参数中使用返回和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;}}