我見過(並持有)有關這方面的各種強烈意見。答案是我認爲目前在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