Mic*_*tum 2015 .net c# exception-handling exception
不鼓励简单地抓住System.Exception
.相反,只应捕获"已知"异常.
现在,这有时会导致不必要的重复代码,例如:
try
{
WebId = new Guid(queryString["web"]);
}
catch (FormatException)
{
WebId = Guid.Empty;
}
catch (OverflowException)
{
WebId = Guid.Empty;
}
Run Code Online (Sandbox Code Playgroud)
我想知道:有没有办法捕获两个异常并且只WebId = Guid.Empty
调用一次呼叫?
给出的例子相当简单,因为它只是一个GUID
.但是想象一下你多次修改一个对象的代码,如果其中一个操作以预期的方式失败,你想要"重置"它object
.但是,如果出现意外异常,我仍然希望将其提高.
Jos*_*gle 2011
抓住System.Exception
并打开类型
catch (Exception ex)
{
if (ex is FormatException || ex is OverflowException)
{
WebId = Guid.Empty;
return;
}
throw;
}
Run Code Online (Sandbox Code Playgroud)
Cra*_*aig 524
编辑:我同意其他人的意见,从C#6.0开始,异常过滤器现在是一个非常好的方法:catch (Exception ex) when (ex is ... || ex is ... )
除了我仍然讨厌一条长线布局,并且会像下面那样亲自编写代码.我认为这是美学的功能,因为我认为它提高了理解力.有些人可能不同意:
catch (Exception ex) when (
ex is ...
|| ex is ...
|| ex is ...
)
Run Code Online (Sandbox Code Playgroud)
原版的:
我知道我在这里参加聚会有点晚了,但圣烟......
直接追逐,这种复制更早的答案,但如果你真的想要为几种异常类型执行一个共同的操作,并保持整个事情在一个方法的范围内整洁,为什么不只是使用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 ); }
}
Run Code Online (Sandbox Code Playgroud)
我不禁怀疑(警告:前面有点讽刺/讽刺)为什么在地球上去做所有这些努力基本上只是替换以下内容:
try
{
// try some stuff
}
catch( FormatException ex ){}
catch( OverflowException ex ){}
catch( ArgumentNullException ex ){}
Run Code Online (Sandbox Code Playgroud)
...对于下一个代码气味的一些疯狂的变化,我的意思是例子,只是假装你正在保存一些按键.
// 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;
}
Run Code Online (Sandbox Code Playgroud)
因为它肯定不会自动更具可读性.
当然,我离开/* write to a log, whatever... */ return;
了第一个例子中的三个相同的实例.
但这是我的观点.你们都听说过功能/方法,对吧?认真.编写一个通用ErrorHandler
函数,就像从每个catch块中调用它一样.
如果你问我,第二个例子(带有if
和is
关键字)的可读性要低得多,同时在项目的维护阶段也容易出错.
维护阶段,对于任何可能相对较新的编程人员而言,将占项目整体生命周期的98.7%或更多,并且做维护的穷人schmuck几乎肯定会成为除你以外的其他人.并且他们很有可能将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;
}
Run Code Online (Sandbox Code Playgroud)
Joe*_*Joe 332
正如其他人所指出的那样,你可以if
在catch块中有一个语句来确定发生了什么.C#6支持异常过滤器,因此以下内容将起作用:
try { … }
catch (Exception e) when (MyFilter(e))
{
…
}
Run Code Online (Sandbox Code Playgroud)
该MyFilter
方法可能看起来像这样:
private bool MyFilter(Exception e)
{
return e is ArgumentNullException || e is FormatException;
}
Run Code Online (Sandbox Code Playgroud)
或者,这可以全部内联完成(when语句的右侧只需要是一个布尔表达式).
try { … }
catch (Exception e) when (e is ArgumentNullException || e is FormatException)
{
…
}
Run Code Online (Sandbox Code Playgroud)
这与使用块if
内的语句不同catch
,使用异常过滤器不会展开堆栈.
您可以下载Visual Studio 2015来检查这一点.
如果要继续使用Visual Studio 2013,可以安装以下nuget包:
安装包Microsoft.Net.Compilers
引用此包将导致使用包中包含的特定版本的C#和Visual Basic编译器构建项目,而不是任何系统安装版本.
Gre*_*ech 186
不幸的是,不是在C#中,因为你需要一个异常过滤器来做这件事而且C#不公开MSIL的这个特性.VB.NET确实具有这种功能,例如
Catch ex As Exception When TypeOf ex Is FormatException OrElse TypeOf ex Is OverflowException
Run Code Online (Sandbox Code Playgroud)
你可以做的是使用匿名函数来封装你的错误代码,然后在那些特定的catch块中调用它:
Action onError = () => WebId = Guid.Empty;
try
{
// something
}
catch (FormatException)
{
onError();
}
catch (OverflowException)
{
onError();
}
Run Code Online (Sandbox Code Playgroud)
Ath*_*ari 131
为了完整起见,从.NET 4.0开始,代码可以重写为:
Guid.TryParse(queryString["web"], out WebId);
Run Code Online (Sandbox Code Playgroud)
TryParse永远不会抛出异常,如果格式错误则返回false,将WebId设置为Guid.Empty
.
从C#7开始,您可以避免在单独的行中引入变量:
Guid.TryParse(queryString["web"], out Guid webId);
Run Code Online (Sandbox Code Playgroud)
您还可以创建用于解析返回元组的方法,这些方法自版本4.6起在.NET Framework中不可用:
(bool success, Guid result) TryParseGuid(string input) =>
(Guid.TryParse(input, out Guid result), result);
Run Code Online (Sandbox Code Playgroud)
并像这样使用它们:
WebId = TryParseGuid(queryString["web"]).result;
// or
var tuple = TryParseGuid(queryString["web"]);
WebId = tuple.success ? tuple.result : DefaultWebId;
Run Code Online (Sandbox Code Playgroud)
当在C#12中实现out-parameters的解构时,接下来对这个无用的答案进行无用的更新.:)
Mat*_*t J 83
现在,c#6+中提供了异常过滤器.你可以做
try
{
WebId = new Guid(queryString["web"]);
}
catch (Exception ex) when(ex is FormatException || ex is OverflowException)
{
WebId = Guid.Empty;
}
Run Code Online (Sandbox Code Playgroud)
Man*_*ero 74
如果您可以将您的应用程序升级到C#6,那么您很幸运.新的C#版本已经实现了Exception过滤器.所以你可以这样写:
catch (Exception ex) when (ex is FormatException || ex is OverflowException) {
WebId = Guid.Empty;
}
Run Code Online (Sandbox Code Playgroud)
有些人认为这段代码是一样的
catch (Exception ex) {
if (ex is FormatException || ex is OverflowException) {
WebId = Guid.Empty;
}
throw;
}
Run Code Online (Sandbox Code Playgroud)
但事实并非如此.实际上,这是C#6中唯一一个在以前版本中无法模拟的新功能.首先,重新投掷意味着比跳过捕获更多的开销.其次,它在语义上不等同.在调试代码时,新功能可以保持堆栈完好无损.如果没有此功能,崩溃转储就不那么有用甚至无用了.
请参阅CodePlex上有关此问题的讨论.一个显示差异的例子.
Tam*_*red 32
如果你不希望使用if
的内声明catch
范围,在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;
}
Run Code Online (Sandbox Code Playgroud)
这段代码Exception
只会在它是a InvalidDataException
或者时才会捕获ArgumentNullException
.
实际上,您可以在该when
子句中基本上放置任何条件:
static int a = 8;
...
catch (Exception exception) when (exception is InvalidDataException && a == 8)
{
Console.WriteLine("Catch");
}
Run Code Online (Sandbox Code Playgroud)
请注意,与范围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");
}
Run Code Online (Sandbox Code Playgroud)
输出:一般捕获.
如果有多个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");
}
Run Code Online (Sandbox Code Playgroud)
输出:捕获.
正如你可以在看MSIL
代码不翻译成if
语句,而是Filters
,并且Exceptions
无法从区域内扔标有Filter 1
和Filter 2
,但过滤器抛Exception
反而会失败,也是最后的比较值被推到堆栈中前endfilter
命令将确定过滤器的成功/失败(Catch 1
XOR Catch 2
将相应地执行):
另外,具体Guid
有Guid.TryParse
方法.
Nec*_*ann 24
C# 9 更新
使用C# 9 中新的模式匹配增强功能,您可以缩短异常过滤器中的表达式。现在,捕获多个异常很简单:
try
{
WebId = new Guid(queryString["web"]);
}
catch (Exception e) when (e is FormatException or OverflowException)
{
WebId = Guid.Empty;
}
Run Code Online (Sandbox Code Playgroud)
Fab*_*ian 22
使用C#7 ,可以改进Michael Stum的答案,同时保持switch语句的可读性:
catch (Exception ex)
{
switch (ex)
{
case FormatException _:
case OverflowException _:
WebId = Guid.Empty;
break;
default:
throw;
}
}
Run Code Online (Sandbox Code Playgroud)
Mat*_*att 20
接受的答案似乎是可以接受的,除了CodeAnalysis/FxCop会抱怨它正在捕获一般异常类型.
此外,似乎"是"运营商可能会略微降低性能.
CA1800:不要不必要地说"考虑测试'作为'运算符的结果",但是如果你这样做,你将编写的代码比单独捕获每个异常的代码要多.
无论如何,这就是我要做的:
bool exThrown = false;
try
{
// Something
}
catch (FormatException) {
exThrown = true;
}
catch (OverflowException) {
exThrown = true;
}
if (exThrown)
{
// Something else
}
Run Code Online (Sandbox Code Playgroud)
SHM*_*SHM 19
在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");
}
Run Code Online (Sandbox Code Playgroud)
bsa*_*ara 18
这是Matt答案的变体(我觉得这有点清洁)...使用方法:
public void TryCatch(...)
{
try
{
// something
return;
}
catch (FormatException) {}
catch (OverflowException) {}
WebId = Guid.Empty;
}
Run Code Online (Sandbox Code Playgroud)
将抛出任何其他异常并且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
}
Run Code Online (Sandbox Code Playgroud)
Ste*_*n T 17
Joseph Daigle的答案是一个很好的解决方案,但我发现以下结构有点整洁且不易出错.
catch(Exception ex)
{
if (!(ex is SomeException || ex is OtherException)) throw;
// Handle exception
}
Run Code Online (Sandbox Code Playgroud)
反转表达式有一些优点:
它甚至可以压缩成一条线(虽然不是很漂亮)
catch(Exception ex) { if (!(ex is SomeException || ex is OtherException)) throw;
// Handle exception
}
Run Code Online (Sandbox Code Playgroud)
编辑: C#6.0中 的异常过滤将使语法更清晰,并且与当前任何解决方案相比还具有许多其他优点.(最值得注意的是让堆栈不受伤害)
以下是使用C#6.0语法的相同问题:
catch(Exception ex) when (ex is SomeException || ex is OtherException)
{
// Handle exception
}
Run Code Online (Sandbox Code Playgroud)
Fly*_*wat 16
@Micheal
您的代码略有修改版本:
catch (Exception ex)
{
Type exType = ex.GetType();
if (exType == typeof(System.FormatException) ||
exType == typeof(System.OverflowException)
{
WebId = Guid.Empty;
} else {
throw;
}
}
Run Code Online (Sandbox Code Playgroud)
字符串比较是丑陋和缓慢的.
Mau*_*ice 13
怎么样
try
{
WebId = Guid.Empty;
WebId = new Guid(queryString["web"]);
}
catch (FormatException)
{
}
catch (OverflowException)
{
}
Run Code Online (Sandbox Code Playgroud)
Kon*_*rin 12
catch (Exception ex)
{
if (!(
ex is FormatException ||
ex is OverflowException))
{
throw;
}
Console.WriteLine("Hello");
}
Run Code Online (Sandbox Code Playgroud)
naw*_*fal 11
链接中的内容不能直接回答您的问题,但将其扩展为以下内容是微不足道的:
static void Main()
{
Action body = () => { ...your code... };
body.Catch<InvalidOperationException>()
.Catch<BadCodeException>()
.Catch<AnotherException>(ex => { ...handler... })();
}
Run Code Online (Sandbox Code Playgroud)
(基本上提供另一个自动Catch
返回的空重载)
对此更大的问题是为什么.我不认为成本超过这里的收益:)
更新2015-12-15:有关C#6,请参阅/sf/answers/1600545551/.它是一种更清洁,现在是该语言的标准.
面向需要更优雅的解决方案来捕获一次并过滤异常的人,我使用扩展方法,如下所示.
我已经在我的库中使用了这个扩展,最初是为其他目的编写的,但它对于type
检查异常非常有效.另外,imho,它看起来比一堆||
语句更清晰.此外,与接受的答案不同,我更喜欢显式异常处理,因此ex is ...
具有不可取的行为,因为可以将指定的类分配给父类型.
用法
if (ex.GetType().IsAnyOf(
typeof(FormatException),
typeof(ArgumentException)))
{
// Handle
}
else
throw;
Run Code Online (Sandbox Code Playgroud)
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;
}
}
}
Run Code Online (Sandbox Code Playgroud)
完整错误处理示例(复制粘贴到新的控制台应用程序)
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));
}
}
}
Run Code Online (Sandbox Code Playgroud)
两个样本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*/
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
由于我觉得这些答案刚刚触及表面,我试图深入挖掘一下.
所以我们真正想做的是不能编译的东西,比如说:
// Won't compile... damn
public static void Main()
{
try
{
throw new ArgumentOutOfRangeException();
}
catch (ArgumentOutOfRangeException)
catch (IndexOutOfRangeException)
{
// ... handle
}
Run Code Online (Sandbox Code Playgroud)
我们想要这个的原因是因为我们不希望异常处理程序捕获稍后在进程中需要的东西.当然,我们可以捕获一个例外并检查"如果"做什么,但说实话,我们并不真的想要那样做.(FxCop,调试器问题,丑陋)
那么为什么这个代码不会编译 - 我们怎么能以这样的方式破解呢?
如果我们查看代码,我们真正想做的就是转发呼叫.但是,根据MS Partition II,IL异常处理程序块不会像这样工作,在这种情况下这是有意义的,因为这意味着'exception'对象可以有不同的类型.
或者用代码编写它,我们要求编译器做这样的事情(好吧,这不完全正确,但它是我猜的最接近的事情):
// Won't compile... damn
try
{
throw new ArgumentOutOfRangeException();
}
catch (ArgumentOutOfRangeException e) {
goto theOtherHandler;
}
catch (IndexOutOfRangeException e) {
theOtherHandler:
Console.WriteLine("Handle!");
}
Run Code Online (Sandbox Code Playgroud)
这不会编译的原因很明显:'$ exception'对象具有什么类型和值(这里存储在变量'e'中)?我们希望编译器处理这种情况的方式是注意两个异常的公共基类型是'Exception',使用它来包含两个异常的变量,然后只处理捕获的两个异常.在IL中实现它的方式是'过滤器',它可以在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.");
Run Code Online (Sandbox Code Playgroud)
显而易见的缺点是我们不能正确地重新投掷,而且 - 老实说 - 这是一个非常丑陋的解决方案.通过执行分支消除可以稍微修复丑陋,这使得解决方案略微更好:
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 :-)");
}
Run Code Online (Sandbox Code Playgroud)
这只留下'重新抛出'.为了实现这一点,我们需要能够在'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.");
Run Code Online (Sandbox Code Playgroud)
另一个解决方案是捕获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;
}
}
Run Code Online (Sandbox Code Playgroud)
总结如下:
这是每个C#开发人员最终面临的经典问题.
让我把你的问题分成两个问题.首先,
我可以一次捕获多个例外吗?
简而言之,没有.
这导致了下一个问题,
如果我无法在同一个catch()块中捕获多个异常类型,我如何避免编写重复的代码?
鉴于您的特定样本,后备值构建起来很便宜,我喜欢按照以下步骤操作:
所以代码看起来像:
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) {}
Run Code Online (Sandbox Code Playgroud)
如果抛出任何异常,则WebId永远不会设置为半构造值,并且仍为Guid.Empty.
如果构造回退值很昂贵,并且重置值要便宜得多,那么我会将重置代码移动到它自己的函数中:
try
{
WebId = new Guid(queryString["web"]);
// More initialization code goes here.
}
catch (FormatException) {
Reset(WebId);
}
catch (OverflowException) {
Reset(WebId);
}
Run Code Online (Sandbox Code Playgroud)
请注意,我确实找到了一种方法,但这看起来更像是The Daily WTF 的材料:
catch (Exception ex)
{
switch (ex.GetType().Name)
{
case "System.FormatException":
case "System.OverflowException":
WebId = Guid.Empty;
break;
default:
throw;
}
}
Run Code Online (Sandbox Code Playgroud)
所以你在每个异常开关中重复大量的代码?听起来像提取方法会是上帝的想法,不是吗?
所以你的代码归结为:
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 */ }
Run Code Online (Sandbox Code Playgroud)
我想知道为什么没有人注意到代码重复.
从C#6开始,您还可以使用其他人已经提到的异常过滤器.所以你可以修改上面的代码:
try { ... }
catch(Exception e) when(e is Exception1 || e is Exception2)
{
Reset(instance);
}
Run Code Online (Sandbox Code Playgroud)
想在这个已经很长的帖子中添加我的简短答案。没有提到的是 catch 语句的优先顺序,更具体地说,您需要了解尝试捕获的每种异常类型的范围。
例如,如果您使用“catch-all”异常作为Exception,它将优先于所有其他 catch 语句,并且您显然会收到编译器错误,但是如果您颠倒顺序,您可以链接您的 catch 语句(我认为有点反模式) )您可以将 catch-all Exception类型放在底部,这将捕获 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);
}
Run Code Online (Sandbox Code Playgroud)
我强烈建议人们查看此 MSDN 文档:
归档时间: |
|
查看次数: |
523041 次 |
最近记录: |