我见过(并持有)有关这方面的各种强烈意见。答案是我认为目前在C#中没有理想的方法。
有一点我觉得(以Java思想的方式)异常是方法二进制接口的一部分,与返回类型和参数类型一样多。但在C#中,它根本不是。这从事实上是清楚的,没有抛出规范系统。
换句话说,如果您希望采取只有您的异常类型应该从您的库方法中跳出来的态度,那么您的客户不会依赖于您的库的内部细节。但很少有图书馆会这么做。
官方C#团队的建议是捕获可能由方法抛出的每种特定类型,如果您认为可以处理它们。不要抓住任何你无法处理的东西。这意味着不需要在库边界处封装内部异常。
但是反过来说,这意味着您需要对给定方法可能抛出的内容进行完美的记录。现代应用程序依赖于第三方库的迅速发展。如果他们都试图捕获特定的异常类型,这些异常类型在将来的库版本组合中可能是不正确的,并且没有编译时检查,那么它就会有一个静态类型系统的嘲弄。
所以有人这样做:
try
{
}
catch (Exception x)
{
// log the message, the stack trace, whatever
}
的问题是,这种捕获所有的异常,包括那些从根本上表明存在严重的问题,比如一个空引用异常。这意味着该程序处于未知状态。检测到的时刻,它应该在它对用户的持久数据造成一些损坏(开始垃圾文件,数据库记录等)之前关闭。
这里隐藏的问题是try/finally。这是一个非常棒的语言功能 - 事实上它非常重要 - 但是如果一个足够严重的例外情况出现,那么它是否真的会导致最终的块运行?如果有错误发生,您是否真的希望证据被破坏?如果程序处于未知状态,那么任何重要的东西都可能被那些最终块破坏。
所以,你真正想要的是什么(更新为C#6!):
try
{
// attempt some operation
}
catch (Exception x) when (x.IsTolerable())
{
// log and skip this operation, keep running
}
在这个例子中,你会写IsTolerable
作为Exception
扩展方法,如果最里面的例外是NullReferenceException
返回false
,IndexOutOfRangeException
,InvalidCastException
或您任何其他异常类型已经决定必须表明,必须停止执行,并要求调查一个低级错误。这些是“无法忍受”的情况。
这可能被称为“乐观”的异常处理:假设所有的例外是,除了一组已知黑名单类型的容忍。另一种方法(由C#5及更早版本支持)是“悲观”方法,只有已知白名单的异常被认为是可容忍的,其他任何东西都是未处理的。
年前悲观的做法是官方推荐的立场。但是现在CLR本身会捕获Task.Run
中的所有异常,因此它可以在线程之间移动错误。这最终导致块执行。所以CRL是非常乐观的默认情况下。
您还可以争取与AppDomain.UnhandledException事件,尽可能多的信息保存为可以用于支持目的(至少堆栈跟踪),然后调用Environment.FailFast到关闭过程之前的任何finally
块可以执行(这可能会破坏宝贵的调查错误所需的信息,或抛出隐藏原始错误的其他例外情况)。
您的第一个示例是C#的反模式,使用'throw;'来代替,否则您将堆栈跟踪重置为当前的throw点。只要使用'throw;'就会保持原始的堆栈跟踪。 – 2010-02-05 22:21:35
双重复制:http://stackoverflow.com/questions/178456/what-is-the-proper-way-to-re-throw-an-exception-in-c和http://stackoverflow.com/questions/ 22623 /净投掷例外的最佳实践。 – 2010-02-05 22:32:20
请注意,即使您使用catch更改了异常的类型(Exception ex),也可以保留堆栈跟踪{throw new MyCustomException(ex); }如果您在MyCustomException类中使用Exception类的提供构造函数(就像所有的.NET Exception类型一样)。 – dbemerlin 2010-02-05 22:52:47