抓到多例外情况,在一次?
-
02-07-2019 - |
题
这是鼓励只是赶 System.Exception
.相反,只有"已知的"例外情况应该被抓住。
现在,这有时会导致不必要的重复代码,例如:
try
{
WebId = new Guid(queryString["web"]);
}
catch (FormatException)
{
WebId = Guid.Empty;
}
catch (OverflowException)
{
WebId = Guid.Empty;
}
我想知道:有没有办法抓住这两个例外,只有电话的 WebId = Guid.Empty
呼叫一次的?
给出的实例是相当简单,因为它是唯一一个 GUID
.但是想象一下代码在哪里你修改的对象多次,如果其中一个操作未能在预期的方式,你想到"重置" object
.然而,如果没有一个意想不到的例外,我仍然想把这更高。
解决方案
Catch System.Exception
并打开类型
catch (Exception ex)
{
if (ex is FormatException || ex is OverflowException)
{
WebId = Guid.Empty;
return;
}
throw;
}
其他提示
编辑:我同意其他人的观点,从C#6.0开始,异常过滤器现在是一个非常好的方法: catch(Exception ex)when(ex is) ... || ex是...)
除了我仍然讨厌一个长线布局,并且会像下面那样亲自编写代码。我认为这是美学的功能,因为我认为它提高了理解力。有些人可能不同意:
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;
}
因为它肯定不会自动更具可读性。
当然,我把三个相同的 / *写实例留给了一个日志,无论是...... * / return;
都是第一个例子。
但这是我的观点。你们都听说过功能/方法,对吧?认真。编写一个常见的 ErrorHandler
函数,就像从每个catch块中调用它一样。
如果你问我,第二个例子(使用 if
和是
关键字)的可读性明显降低,同时在维护阶段更容易出错你的项目。
维护阶段,对于任何可能相对较新的编程人员而言,将占项目整体生命周期的98.7%或更多,并且做维护的穷人schmuck几乎肯定会成为除你以外的其他人。并且很有可能他们将50%的时间花在诅咒你名字的工作上。
当然,FxCop会咆哮你,所以你必须 为你的代码添加一个属性,该属性具有与正在运行的程序完全相同的zip,并且只是那里告诉FxCop忽略一个问题,在99.9%的情况下,它在标记中是完全正确的。而且,对不起,我可能会弄错,但不是“忽略”属性最终实际编译到您的应用程序中?
将整个 if
测试放在一行上会使它更具可读性吗?我不这么认为。我的意思是,我确实让另一位程序员在很久以前激烈地争辩说,将更多代码放在一行上会使其“运行得更快”。但他当然是疯狂的坚果。试图向他解释(有一个直面 - 这很有挑战性)解释器或编译器如何将这条长线分开为离散的单指令每行语句 - 基本上与结果相同如果他继续前进只是使代码可读而不是试图超越编译器 - 对他没有任何影响。但我离题了。
从现在起一个月或两个月再添加三种异常类型时,这会获得多少更少的可读性? (答案:它的 很多 可读性较差)。
其中一个主要观点是,我们每天都在关注的文本源代码格式化的大部分内容是让其他人真正地,非常明显地知道什么是实际的
正如其他人所指出的那样,你可以在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
引用此包将导致使用the来构建项目 包含在C#和Visual Basic编译器中的特定版本 包,而不是任何系统安装版本。
不幸的是,不是在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();
}
为了完整起见,由于 .NET 4.0 ,代码可以重写为:
Guid.TryParse(queryString["web"], out WebId);
TryParse 从不抛出异常并返回false格式错误,将WebId设置为 Guid.Empty
。
由于 C#7 ,您可以避免在单独的行中引入变量:
Guid.TryParse(queryString["web"], out Guid webId);
您还可以创建用于解析返回元组的方法,这些方法自版本4.6起在.NET Framework中不可用:
(bool success, Guid result) TryParseGuid(string input) =>
(Guid.TryParse(input, out Guid result), result);
并像这样使用它们:
WebId = TryParseGuid(queryString["web"]).result;
// or
var tuple = TryParseGuid(queryString["web"]);
WebId = tuple.success ? tuple.result : DefaultWebId;
当在C#12中实现out-parameters的解构时,接下来对这个无用的答案进行无用的更新:)
如果您可以将您的应用程序升级到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的讨论。以及显示差异的示例。
现在,c#6+中提供了异常过滤器。你可以做到
try
{
WebId = new Guid(queryString["web"]);
}
catch (Exception ex) when(ex is FormatException || ex is OverflowException)
{
WebId = Guid.Empty;
}
在C#7.0+中,您也可以将它与模式匹配结合起来
try
{
await Task.WaitAll(tasks);
}
catch (Exception ex) when( ex is AggregateException ae )
{
//do something with members of ae, say ae.InnerExceptions
}
如果您不想在 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
时,此代码才会捕获 Exception
。
实际上,当子句
时,基本上可以将任何条件放在中
static int a = 8;
...
catch (Exception exception) when (exception is InvalidDataException && a == 8)
{
Console.WriteLine("Catch");
}
请注意,与 catch
的范围内的 if
语句相反,异常过滤器
不能抛出异常
,当它们发生时,或当条件不是 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
异常过滤器
时 - 第一个将被接受:
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 1
和 Filter 2
的区域内抛出,但抛出 Exception
的过滤器会失败,在 endfilter
命令之前推送到堆栈的最后一个比较值将确定过滤器的成功/失败( Catch 1
XOR Catch 2
将相应执行):
此外,具体的 Guid
具有 Guid.TryParse
方法。
接受的答案似乎可以接受,但CodeAnalysis / FxCop 会抱怨它的事实是捕获一般异常类型。
此外,似乎“是”。运算符可能会略微降低性能。
CA1800:不要不必要地施放 说“考虑测试'as'运算符的结果而不是”,但如果你这样做,那么你将编写的代码多于单独捕获每个异常的代码。
无论如何,这就是我要做的事情:
bool exThrown = false;
try
{
// Something
}
catch (FormatException) {
exThrown = true;
}
catch (OverflowException) {
exThrown = true;
}
if (exThrown)
{
// Something else
}
在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#7可以改进 Michael Stum的答案,同时保持switch语句的可读性:
catch (Exception ex)
{
switch (ex)
{
case FormatException _:
case OverflowException _:
WebId = Guid.Empty;
break;
default:
throw;
}
}
这是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
}
约瑟夫*英对照精华文章的回答 是一个很好的解决方案,但是我发现了下面的结构是一位整洁和不易出错。
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
}
@Micheal
您的代码略有修订版本:
catch (Exception ex)
{
Type exType = ex.GetType();
if (exType == typeof(System.FormatException) ||
exType == typeof(System.OverflowException)
{
WebId = Guid.Empty;
} else {
throw;
}
}
字符串比较是丑陋而缓慢的。
怎么样
try
{
WebId = Guid.Empty;
WebId = new Guid(queryString["web"]);
}
catch (FormatException)
{
}
catch (OverflowException)
{
}
警告和警告:另一种,功能风格。
链接中的内容并未直接回答您的问题,但将其扩展为以下内容是微不足道的:
static void Main()
{
Action body = () => { ...your code... };
body.Catch<InvalidOperationException>()
.Catch<BadCodeException>()
.Catch<AnotherException>(ex => { ...handler... })();
}
(基本上提供另一个自动返回的空 Catch
重载)
更重要的问题是为什么。我不认为成本超过了这里的收益:)
catch (Exception ex)
{
if (!(
ex is FormatException ||
ex is OverflowException))
{
throw;
}
Console.WriteLine("Hello");
}
2015-12-15更新:有关C,请参阅 https://stackoverflow.com/a/22864936/1718702 #6。它是一种更清洁,现在是该语言的标准。
面向那些希望更优雅的解决方案能够捕获一次并过滤异常的用户,我使用了扩展方法下方。
我的库中已经有了这个扩展,最初是为其他目的编写的,但它对于 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*/
}
}
}
}
因为我觉得这些答案只是触动了表面上,我试图要挖深一点.
那么我们将真正想做的是什么,并不编制的,说:
// Won't compile... damn
public static void Main()
{
try
{
throw new ArgumentOutOfRangeException();
}
catch (ArgumentOutOfRangeException)
catch (IndexOutOfRangeException)
{
// ... handle
}
我们希望这是因为我们不想要的例外处理程序,以赶上东西,我们需要在以后的过程。当然,我们可以抓住的一个例外,并检查有一个"如果"该怎么做,但我们是诚实的,我们真的不希望这样。(FxCop、调试器问题,uglyness)
所以你为什么不这个代码汇编和我们如何可以破解它在这样一种方式,它会?
如果我们看一看码,我们真正想要做的就是向前的呼叫。然而,根据MS分区II,IL例外处理程序的区块不会像这样工作,而在这种情况下是有意义的,因为这将意味着'例外'的对象可以有不同的类型。
或者把它写在代码,我们要求编译器做这样的事(那么这是不完全正确的,但这是可能最接近的事情,我猜):
// Won't compile... damn
try
{
throw new ArgumentOutOfRangeException();
}
catch (ArgumentOutOfRangeException e) {
goto theOtherHandler;
}
catch (IndexOutOfRangeException e) {
theOtherHandler:
Console.WriteLine("Handle!");
}
原因,这不会编制是相当显而易见的:什么类型和价值的'$例外'对象(这是这里的存在的变量'e')?我们想要的方式编译器处理这个问的是,要注意的共同基本类型的两个例外情况是例外',使用这一变量包含这两个例外情况,然后把手只有两个例外被抓住了。这是实现在IL是作为"过滤器",这是在VB.Net.
要使它的工作中,我们需要一个暂时的变量具有正确'的例外'的基类型。控制流量的代码,我们可以添加一些分支机构。这里云:
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.");
显而易见的缺点是,我们不能重新引发正常-好吧,让我们以诚实的-那很丑陋的解决方案。该uglyness可以是固定的一点通过执行支消除,这使得解决方案稍有更好的:
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 :-)");
}
留下的只是重新扔'.对于这项工作,我们需要能够执行处理内部的'捕'块-只有这样,才能使这项工作是通过一个醒目的'例外'的对象。
在这一点上,我们可以添加一个单独的功能用于处理不同类型的例外情况使用载的决议,或来处理异常。既有缺点。开始,这里的方式做到这一带有辅助功能:
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.");
和其他的解决方案是抓除对象及处理它。最直译为此,基于上述背景是:
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和你的调试器的未捕获的例外情况。
这是每个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);
}
所以你&#180;在每个异常开关中重复大量的代码?听起来像提取方法会是上帝的想法,不是吗?
所以你的代码归结为:
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语句之前,你显然会遇到编译器错误,但是如果你颠倒顺序,你可以链接你的catch语句(我认为反模式的一点)你可以把底部的全部异常类型,这将捕获任何在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文档:
也许尝试保持代码简单,例如将公共代码放在方法中,就像在代码中不在catch子句中的任何其他部分一样?
E.g:
try
{
// ...
}
catch (FormatException)
{
DoSomething();
}
catch (OverflowException)
{
DoSomething();
}
// ...
private void DoSomething()
{
// ...
}
我是怎么做到的,试图找到 simple is beautiful 模式
请注意,我确实找到了一种方法,但这看起来更像是 The Daily WTF的材料:
catch (Exception ex)
{
switch (ex.GetType().Name)
{
case "System.FormatException":
case "System.OverflowException":
WebId = Guid.Empty;
break;
default:
throw;
}
}
这里值得一提。您可以响应多个组合(异常错误和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。我没有对代码简单性进行空检查,考虑添加参数验证。
p.s.2 如果要从catch返回一个值,则必须执行相同的Catch方法,但是在参数中使用return和Func而不是Action。
只需调用try并捕获两次。
try
{
WebId = new Guid(queryString["web"]);
}
catch (FormatException)
{
WebId = Guid.Empty;
}
try
{
WebId = new Guid(queryString["web"]);
}
catch (OverflowException)
{
WebId = Guid.Empty;
}
只是简单!!
在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");
}
}