提问人:Michael Stum 提问时间:9/26/2008 最后编辑:Grigory ZhadkoMichael Stum 更新时间:11/12/2023 访问量:823215
一次捕获多个异常?
Catch multiple exceptions at once?
问:
不鼓励简单地抓住.相反,只应捕获“已知”异常。System.Exception
现在,这有时会导致不必要的重复代码,例如:
try
{
WebId = new Guid(queryString["web"]);
}
catch (FormatException)
{
WebId = Guid.Empty;
}
catch (OverflowException)
{
WebId = Guid.Empty;
}
我想知道:有没有办法捕获两个异常并且只调用一次调用?WebId = Guid.Empty
给定的示例相当简单,因为它只是一个 GUID
。但是想象一下,在代码中,您多次修改一个对象,如果其中一个操作意外失败,则要“重置”.但是,如果出现意外异常,我仍然想把它扔得更高。object
答:
请注意,我确实找到了一种方法,但这看起来更像是 The Daily 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;
}
else
throw;
}
评论
ExceptionDispatchInfo.Capture(ex).Throw();
throw;
@Micheal
代码的略微修改版本:
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
{
// 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 中实现输出参数的解构时。:)
告诫和警告:另一种功能性风格。
链接中的内容并不能直接回答您的问题,但将其扩展为如下所示是微不足道的:
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/closure/inline 函数来执行如下操作呢?我的意思是,很有可能你最终会意识到你只是想让这个闭包成为一个单独的方法,你可以在任何地方使用。但是,在不实际更改代码其余结构的情况下,做到这一点将非常容易。右?
private void TestMethod ()
{
Action<Exception> errorHandler = ( ex ) => {
// write to a log, whatever...
};
try
{
// try some stuff
}
catch ( FormatException ex ) { errorHandler ( ex ); }
catch ( OverflowException ex ) { errorHandler ( ex ); }
catch ( ArgumentNullException ex ) { errorHandler ( ex ); }
}
我不禁想知道(警告:前面有点讽刺/讽刺)为什么要付出所有这些努力,基本上只是替换以下内容:
try
{
// try some stuff
}
catch( FormatException ex ){}
catch( OverflowException ex ){}
catch( ArgumentNullException ex ){}
...下一个代码气味的一些疯狂变化,我的意思是例子,只是假装你正在保存一些击键。
// sorta sucks, let's be honest...
try
{
// try some stuff
}
catch( Exception ex )
{
if (ex is FormatException ||
ex is OverflowException ||
ex is ArgumentNullException)
{
// write to a log, whatever...
return;
}
throw;
}
因为它肯定不会自动提高可读性。
当然,我在第一个示例中保留了三个相同的实例。/* write to a log, whatever... */ return;
但这就是我的观点。你们都听说过函数/方法,对吧?认真地。编写一个通用函数,然后从每个 catch 块中调用它。ErrorHandler
如果你问我,第二个例子(带有 和 关键字)的可读性明显降低,同时在项目的维护阶段更容易出错。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 更新:请参阅 C#6 的 https://stackoverflow.com/a/22864936/1718702。它更干净,现在是语言的标准。
为了满足那些想要一个更优雅的解决方案来捕获一次并过滤异常的人,我使用了一个扩展方法,如下所示。
我的库中已经有这个扩展,最初是为其他目的而写的,但它非常适合检查异常。另外,恕我直言,它看起来比一堆陈述更干净。此外,与公认的答案不同,我更喜欢显式异常处理,因此具有不希望的行为,因为派生类可以分配给父类型)。type
||
ex is ...
用法
if (ex.GetType().IsAnyOf(
typeof(FormatException),
typeof(ArgumentException)))
{
// Handle
}
else
throw;
IsAnyOf.cs 扩展(请参阅依赖项的完整错误处理示例)
namespace Common.FluentValidation
{
public static partial class Validate
{
/// <summary>
/// Validates the passed in parameter matches at least one of the passed in comparisons.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="p_parameter">Parameter to validate.</param>
/// <param name="p_comparisons">Values to compare against.</param>
/// <returns>True if a match is found.</returns>
/// <exception cref="ArgumentNullException"></exception>
public static bool IsAnyOf<T>(this T p_parameter, params T[] p_comparisons)
{
// Validate
p_parameter
.CannotBeNull("p_parameter");
p_comparisons
.CannotBeNullOrEmpty("p_comparisons");
// Test for any match
foreach (var item in p_comparisons)
if (p_parameter.Equals(item))
return true;
// Return no matches found
return false;
}
}
}
完整错误处理示例(复制粘贴到新的控制台应用)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Common.FluentValidation;
namespace IsAnyOfExceptionHandlerSample
{
class Program
{
static void Main(string[] args)
{
// High Level Error Handler (Log and Crash App)
try
{
Foo();
}
catch (OutOfMemoryException ex)
{
Console.WriteLine("FATAL ERROR! System Crashing. " + ex.Message);
Console.ReadKey();
}
}
static void Foo()
{
// Init
List<Action<string>> TestActions = new List<Action<string>>()
{
(key) => { throw new FormatException(); },
(key) => { throw new ArgumentException(); },
(key) => { throw new KeyNotFoundException();},
(key) => { throw new OutOfMemoryException(); },
};
// Run
foreach (var FooAction in TestActions)
{
// Mid-Level Error Handler (Appends Data for Log)
try
{
// Init
var SomeKeyPassedToFoo = "FooParam";
// Low-Level Handler (Handle/Log and Keep going)
try
{
FooAction(SomeKeyPassedToFoo);
}
catch (Exception ex)
{
if (ex.GetType().IsAnyOf(
typeof(FormatException),
typeof(ArgumentException)))
{
// Handle
Console.WriteLine("ex was {0}", ex.GetType().Name);
Console.ReadKey();
}
else
{
// Add some Debug info
ex.Data.Add("SomeKeyPassedToFoo", SomeKeyPassedToFoo.ToString());
throw;
}
}
}
catch (KeyNotFoundException ex)
{
// Handle differently
Console.WriteLine(ex.Message);
int Count = 0;
if (!Validate.IsAnyNull(ex, ex.Data, ex.Data.Keys))
foreach (var Key in ex.Data.Keys)
Console.WriteLine(
"[{0}][\"{1}\" = {2}]",
Count, Key, ex.Data[Key]);
Console.ReadKey();
}
}
}
}
}
namespace Common.FluentValidation
{
public static partial class Validate
{
/// <summary>
/// Validates the passed in parameter matches at least one of the passed in comparisons.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="p_parameter">Parameter to validate.</param>
/// <param name="p_comparisons">Values to compare against.</param>
/// <returns>True if a match is found.</returns>
/// <exception cref="ArgumentNullException"></exception>
public static bool IsAnyOf<T>(this T p_parameter, params T[] p_comparisons)
{
// Validate
p_parameter
.CannotBeNull("p_parameter");
p_comparisons
.CannotBeNullOrEmpty("p_comparisons");
// Test for any match
foreach (var item in p_comparisons)
if (p_parameter.Equals(item))
return true;
// Return no matches found
return false;
}
/// <summary>
/// Validates if any passed in parameter is equal to null.
/// </summary>
/// <param name="p_parameters">Parameters to test for Null.</param>
/// <returns>True if one or more parameters are null.</returns>
public static bool IsAnyNull(params object[] p_parameters)
{
p_parameters
.CannotBeNullOrEmpty("p_parameters");
foreach (var item in p_parameters)
if (item == null)
return true;
return false;
}
}
}
namespace Common.FluentValidation
{
public static partial class Validate
{
/// <summary>
/// Validates the passed in parameter is not null, throwing a detailed exception message if the test fails.
/// </summary>
/// <param name="p_parameter">Parameter to validate.</param>
/// <param name="p_name">Name of tested parameter to assist with debugging.</param>
/// <exception cref="ArgumentNullException"></exception>
public static void CannotBeNull(this object p_parameter, string p_name)
{
if (p_parameter == null)
throw
new
ArgumentNullException(
string.Format("Parameter \"{0}\" cannot be null.",
p_name), default(Exception));
}
}
}
namespace Common.FluentValidation
{
public static partial class Validate
{
/// <summary>
/// Validates the passed in parameter is not null or an empty collection, throwing a detailed exception message if the test fails.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="p_parameter">Parameter to validate.</param>
/// <param name="p_name">Name of tested parameter to assist with debugging.</param>
/// <exception cref="ArgumentNullException"></exception>
/// <exception cref="ArgumentOutOfRangeException"></exception>
public static void CannotBeNullOrEmpty<T>(this ICollection<T> p_parameter, string p_name)
{
if (p_parameter == null)
throw new ArgumentNullException("Collection cannot be null.\r\nParameter_Name: " + p_name, default(Exception));
if (p_parameter.Count <= 0)
throw new ArgumentOutOfRangeException("Collection cannot be empty.\r\nParameter_Name: " + p_name, default(Exception));
}
/// <summary>
/// Validates the passed in parameter is not null or empty, throwing a detailed exception message if the test fails.
/// </summary>
/// <param name="p_parameter">Parameter to validate.</param>
/// <param name="p_name">Name of tested parameter to assist with debugging.</param>
/// <exception cref="ArgumentException"></exception>
public static void CannotBeNullOrEmpty(this string p_parameter, string p_name)
{
if (string.IsNullOrEmpty(p_parameter))
throw new ArgumentException("String cannot be null or empty.\r\nParameter_Name: " + p_name, default(Exception));
}
}
}
两个示例 NUnit 单元测试
类型的匹配行为是精确的(即。子项不是其任何父类型的匹配项)。Exception
using System;
using System.Collections.Generic;
using Common.FluentValidation;
using NUnit.Framework;
namespace UnitTests.Common.Fluent_Validations
{
[TestFixture]
public class IsAnyOf_Tests
{
[Test, ExpectedException(typeof(ArgumentNullException))]
public void IsAnyOf_ArgumentNullException_ShouldNotMatch_ArgumentException_Test()
{
Action TestMethod = () => { throw new ArgumentNullException(); };
try
{
TestMethod();
}
catch (Exception ex)
{
if (ex.GetType().IsAnyOf(
typeof(ArgumentException), /*Note: ArgumentNullException derrived from ArgumentException*/
typeof(FormatException),
typeof(KeyNotFoundException)))
{
// Handle expected Exceptions
return;
}
//else throw original
throw;
}
}
[Test, ExpectedException(typeof(OutOfMemoryException))]
public void IsAnyOf_OutOfMemoryException_ShouldMatch_OutOfMemoryException_Test()
{
Action TestMethod = () => { throw new OutOfMemoryException(); };
try
{
TestMethod();
}
catch (Exception ex)
{
if (ex.GetType().IsAnyOf(
typeof(OutOfMemoryException),
typeof(StackOverflowException)))
throw;
/*else... Handle other exception types, typically by logging to file*/
}
}
}
}
评论
正如其他人所指出的,您可以在 catch 块中加入一个语句来确定正在发生的事情。C#6 支持异常筛选器,因此以下操作将起作用:if
try { … }
catch (Exception e) when (MyFilter(e))
{
…
}
然后,该方法可能如下所示:MyFilter
private bool MyFilter(Exception e)
{
return e is ArgumentNullException || e is FormatException;
}
或者,这可以全部内联完成(when 语句的右侧只需要是一个布尔表达式)。
try { … }
catch (Exception e) when (e is ArgumentNullException || e is FormatException)
{
…
}
这与在块内使用语句不同,使用异常过滤器不会展开堆栈。if
catch
可以下载 Visual Studio 2015 来查看此内容。
如果要继续使用 Visual Studio 2013,可以安装以下 nuget 包:
安装包 Microsoft.Net.编译器
引用此包将导致使用 包含在 软件包,而不是任何系统安装的版本。
评论
由于我觉得这些答案只是触及表面,所以我试图更深入地挖掘。
因此,我们真正想做的是一些不编译的东西,比如说:
// Won't compile... damn
public static void Main()
{
try
{
throw new ArgumentOutOfRangeException();
}
catch (ArgumentOutOfRangeException)
catch (IndexOutOfRangeException)
{
// ... handle
}
我们之所以需要这样做,是因为我们不希望异常处理程序捕获我们稍后在此过程中需要的内容。当然,我们可以捕获异常并用“if”检查该做什么,但老实说,我们真的不希望这样。(FxCop,调试器问题,丑陋)
那么,为什么这段代码无法编译 - 我们如何才能以这样一种方式对其进行破解呢?
如果我们看一下代码,我们真正想做的是转接呼叫。但是,根据 MS 分区 II,IL 异常处理程序块不会像这样工作,在这种情况下这是有道理的,因为这意味着“异常”对象可以具有不同的类型。
或者要用代码编写它,我们要求编译器做这样的事情(嗯,这并不完全正确,但我想这是最接近的事情):
// Won't compile... damn
try
{
throw new ArgumentOutOfRangeException();
}
catch (ArgumentOutOfRangeException e) {
goto theOtherHandler;
}
catch (IndexOutOfRangeException e) {
theOtherHandler:
Console.WriteLine("Handle!");
}
这不会编译的原因很明显:“$exception”对象将具有什么类型和值(此处存储在变量“e”中)?我们希望编译器处理此问题的方式是注意两个异常的通用基类型是“Exception”,将其用于包含两个异常的变量,然后仅处理捕获的两个异常。在 IL 中实现它的方式是作为“过滤器”,它在 VB.Net 中可用。
为了使它在 C# 中工作,我们需要一个具有正确“Exception”基类型的临时变量。为了控制代码的流程,我们可以添加一些分支。这里是:
Exception ex;
try
{
throw new ArgumentException(); // for demo purposes; won't be caught.
goto noCatch;
}
catch (ArgumentOutOfRangeException e) {
ex = e;
}
catch (IndexOutOfRangeException e) {
ex = e;
}
Console.WriteLine("Handle the exception 'ex' here :-)");
// throw ex ?
noCatch:
Console.WriteLine("We're done with the exception handling.");
这样做的明显缺点是我们不能正确地重新投掷,而且——老实说——这是一个非常丑陋的解决方案。丑陋可以通过执行分支消除来修复一点,这使得解决方案稍微好一点:
Exception ex = null;
try
{
throw new ArgumentException();
}
catch (ArgumentOutOfRangeException e)
{
ex = e;
}
catch (IndexOutOfRangeException e)
{
ex = e;
}
if (ex != null)
{
Console.WriteLine("Handle the exception here :-)");
}
这只剩下“重新投掷”。为此,我们需要能够在 'catch' 块内执行处理 - 而使这项工作的唯一方法是通过捕获 'Exception' 对象。
此时,我们可以添加一个单独的函数,该函数使用重载解析处理不同类型的异常,或者处理异常。两者都有缺点。首先,以下是使用辅助函数执行此操作的方法:
private static bool Handle(Exception e)
{
Console.WriteLine("Handle the exception here :-)");
return true; // false will re-throw;
}
public static void Main()
{
try
{
throw new OutOfMemoryException();
}
catch (ArgumentException e)
{
if (!Handle(e)) { throw; }
}
catch (IndexOutOfRangeException e)
{
if (!Handle(e)) { throw; }
}
Console.WriteLine("We're done with the exception handling.");
另一种解决方案是捕获 Exception 对象并相应地处理它。根据上面的上下文,最直白的翻译是这样的:
try
{
throw new ArgumentException();
}
catch (Exception e)
{
Exception ex = (Exception)(e as ArgumentException) ?? (e as IndexOutOfRangeException);
if (ex != null)
{
Console.WriteLine("Handle the exception here :-)");
// throw ?
}
else
{
throw;
}
}
总而言之:
- 如果我们不想重新抛出,我们可以考虑捕获正确的异常,并将它们存储在临时异常中。
- 如果处理程序很简单,并且我们想重用代码,最好的解决方案可能是引入一个辅助函数。
- 如果我们想重新抛出,我们别无选择,只能将代码放在“异常”捕获处理程序中,这将破坏 FxCop 和调试器的未捕获异常。
Joseph Daigle 的答案是一个很好的解决方案,但我发现下面的结构更整洁,更不容易出错。
catch(Exception ex)
{
if (!(ex is SomeException || ex is OtherException)) throw;
// Handle exception
}
反转表达式有几个优点:
- return 语句不是必需的
- 代码不是嵌套的
- 没有忘记 Joseph 解决方案中与表达式分开的“throw”或“return”语句的风险。
它甚至可以压缩成一条线(虽然不是很漂亮)
catch(Exception ex) { if (!(ex is SomeException || ex is OtherException)) throw;
// Handle exception
}
编辑:C# 6.0 中的异常筛选将使语法更简洁一些,并且与任何当前解决方案相比,它还具有许多其他好处。(最值得注意的是,堆栈不受伤害)
以下是使用 C# 6.0 语法时相同问题的外观:
catch(Exception ex) when (ex is SomeException || ex is OtherException)
{
// Handle exception
}
如果可以将应用程序升级到 C# 6,那么您很幸运。新的 C# 版本已实现异常筛选器。所以你可以这样写:
catch (Exception ex) when (ex is FormatException || ex is OverflowException) {
WebId = Guid.Empty;
}
有些人认为这段代码与
catch (Exception ex) {
if (ex is FormatException || ex is OverflowException) {
WebId = Guid.Empty;
}
else {
throw;
}
}
但事实并非如此。实际上,这是 C# 6 中唯一在以前的版本中无法模拟的新功能。首先,重新投掷意味着比跳过接球更多的开销。其次,它在语义上不等同。在调试代码时,新功能会保持堆栈不变。如果没有此功能,故障转储就不太有用,甚至毫无用处。
在 CodePlex 上查看有关此内容的讨论不再可用。还有一个例子显示了差异。
评论
return
FormatException
OverflowException
else
在 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");
}
}
评论
catch (HttpException e) when e.GetHttpCode() == 400 { WriteLine("Bad Request"; }
在 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");
}
如果不想在作用域内使用语句,则可以在 C# 6.0
中使用异常筛选器
语法,该语法在预览版本中已受 CLR 支持,但仅存在于 / 中:if
catch
VB.NET
MSIL
try
{
WebId = new Guid(queryString["web"]);
}
catch (Exception exception) when (exception is FormatException || ex is OverflowException)
{
WebId = Guid.Empty;
}
此代码将捕获仅当它是 或 .Exception
InvalidDataException
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。
正如您在代码中所看到的,代码不是转换为语句,而是转换为 ,并且不能从标有 和 的区域内抛出,但抛出的过滤器将失败,此外,命令之前推送到堆栈的最后一个比较值将确定过滤器的成功/失败(XOR 将相应地执行):MSIL
if
Filters
Exceptions
Filter 1
Filter 2
Exception
endfilter
Catch 1
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”异常作为 Exception,它将在所有其他 catch 语句之前,并且您显然会得到编译器错误,但是,如果您颠倒顺序,您可以链接您的 catch 语句(我认为有点反模式),您可以将 catch-all Exception 类型放在底部,这将捕获任何不符合您尝试中更高级别的异常。catch 块:
try
{
// do some work here
}
catch (WebException ex)
{
// catch a web excpetion
}
catch (ArgumentException ex)
{
// do some stuff
}
catch (Exception ex)
{
// you should really surface your errors but this is for example only
throw new Exception("An error occurred: " + ex.Message);
}
我强烈建议人们查看此MSDN文档:
这是每个 C# 开发人员最终都会面临的一个经典问题。
让我将您的问题分成 2 个问题。第一个,
我可以一次捕获多个异常吗?
简而言之,没有。
这就引出了下一个问题,
鉴于我无法在同一个 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 注释,这可以通过省略 discard 变量来简化 C# 8:
catch (Exception ex)
{
switch (ex)
{
case FormatException:
case OverflowException:
WebId = Guid.Empty;
break;
default:
throw;
}
}
使用 C# 8 作为开关表达式:
catch (Exception ex)
{
WebId = ex switch
{
_ when ex is FormatException || ex is OverflowException => Guid.Empty,
_ => throw ex
};
}
正如 Nechemia Hoffmann 所指出的。后一个示例将导致堆栈跟踪丢失。通过使用 Jürgen Steinblock 描述的扩展方法在抛出之前捕获堆栈跟踪,可以防止这种情况发生:
catch (Exception ex)
{
WebId = ex switch
{
_ when ex is FormatException || ex is OverflowException => Guid.Empty,
_ => throw ex.Capture()
};
}
public static Exception Capture(this Exception ex)
{
ExceptionDispatchInfo.Capture(ex).Throw();
return ex;
}
可以使用 C# 9 的模式匹配增强功能来简化这两种样式:
catch (Exception ex)
{
switch (ex)
{
case FormatException or OverflowException:
WebId = Guid.Empty;
break;
default:
throw;
}
}
catch (Exception ex)
{
WebId = ex switch
{
_ when ex is FormatException or OverflowException => Guid.Empty,
_ => throw ex.Capture()
};
}
评论
throw ex
_
也许尝试保持代码简单,例如将通用代码放在方法中,就像在代码的任何其他部分中所做的那样,这些部分不在 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。在本例中,返回的 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
}
评论
我想建议最简短的答案(一种更实用的风格):
Catch<FormatException, OverflowException>(() =>
{
WebId = new Guid(queryString["web"]);
},
exception =>
{
WebId = Guid.Empty;
});
为此,您需要创建几个“Catch”方法重载,类似于 System.Action:
[DebuggerNonUserCode]
public static void Catch<TException1, TException2>(Action tryBlock,
Action<Exception> catchBlock)
{
CatchMany(tryBlock, catchBlock, typeof(TException1), typeof(TException2));
}
[DebuggerNonUserCode]
public static void Catch<TException1, TException2, TException3>(Action tryBlock,
Action<Exception> catchBlock)
{
CatchMany(tryBlock, catchBlock, typeof(TException1), typeof(TException2), typeof(TException3));
}
等等,随心所欲。但是你需要做一次,你就可以在所有项目中使用它(或者,如果你创建了一个 nuget 包,我们也可以使用它)。
和 CatchMany 实现:
[DebuggerNonUserCode]
public static void CatchMany(Action tryBlock, Action<Exception> catchBlock,
params Type[] exceptionTypes)
{
try
{
tryBlock();
}
catch (Exception exception)
{
if (exceptionTypes.Contains(exception.GetType())) catchBlock(exception);
else throw;
}
}
p.s. 为了代码简单性,我没有进行空检查,请考虑添加参数验证。
附页2 如果要从 catch 返回值,则必须执行相同的 Catch 方法,但在参数中使用 returns 和 Func,而不是 Action。
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;
}
}
评论