我看到了各种不同的强烈观点。答案是我认为目前在C#中没有理想的方法。
曾经我认为(以Java的方式)异常是方法的二进制接口的一部分,就像返回类型和参数类型一样。但在C#中,它根本不是这样。这一点很明显,因为没有throws规范系统。
换句话说,如果你愿意,你可以采取这样的态度:只有你的异常类型应该从你的库方法中抛出,这样你的客户端就不会依赖于你的库的内部细节。但是很少有库这样做。
官方的C#团队建议是,如果你认为自己能够处理,就捕获每个可能被方法抛出的特定类型。不要捕获任何你真的无法处理的东西。这意味着在库边界处没有封装内部异常。
但反过来,这意味着你需要完美的文档说明给定方法可能会抛出什么异常。现代应用程序依赖于大量的第三方库,这些库正在快速发展。如果它们都试图捕获可能在未来版本的库组合中不正确的特定异常类型,而没有编译时检查,那将对静态类型系统产生嘲笑。因此,人们这样做:
try
{
}
catch (Exception x)
{
}
问题在于它捕获了所有异常类型,包括那些根本表明严重问题的异常,比如空引用异常。这意味着程序处于未知状态。一旦检测到这种情况,它应该在对用户的持久数据造成损害之前关闭(开始损坏文件、数据库记录等)。
这里隐藏的问题是try/finally。它是一个非常好的语言特性 - 实际上是必不可少的 - 但是如果一个足够严重的异常正在向上传播,它真的应该导致finally块运行吗?在当前有错误发生时,你真的希望证据被销毁吗?如果程序处于未知状态,那么那些finally块可能会摧毁任何重要的东西。
所以,你真正需要的是(C# 6的更新!):
try
{
}
catch (Exception x) when (x.IsTolerable())
{
}
在这个例子中,您需要将
IsTolerable
作为
Exception
的扩展方法编写,如果最内层的异常是
NullReferenceException
、
IndexOutOfRangeException
、
InvalidCastException
或任何其他您已经决定必须表示必须停止执行并需要调查的低级错误的异常类型,则返回
false
。这些是“不可容忍”的情况。
这可能被称为“乐观”的异常处理:假设所有异常都是可以容忍的,除了一组已知的黑名单类型。另一种方法(由C# 5及更早版本支持)是“悲观”的方法,其中只有已知的白名单异常被认为是可以容忍的,而其他任何异常都未处理。
多年前,悲观的方法是官方推荐的立场。但是现在CLR本身在
Task.Run
中捕获所有异常,因此它可以在线程之间移动错误。这会导致finally块执行。因此,默认情况下,CRL非常乐观。
您还可以注册
AppDomain.UnhandledException事件,为支持目的保存尽可能多的信息(至少是堆栈跟踪),然后调用
Environment.FailFast在任何
finally
块可以执行之前关闭进程(这可能会破坏需要调查错误的有价值信息,或者抛出隐藏原始异常的其他异常)。
throw;
,否则会将堆栈跟踪重置为当前抛出点。仅使用throw;
将保留原始的堆栈跟踪信息。 - Nick Larsen