標籤:des winform io os 使用 for 檔案 sp on
46, 顯示釋放資源,需要實現IDisposable介面。
最好按照微軟建議的Dispose模式實現。實現了IDisposable介面後,在Using代碼塊中,垃圾會得到自動清理。 47, 即使提供了顯示的釋放方法,也應該在終結器中提供隱式實現。
因為我們不能保證使用者會主動去調用這個釋放方法,但我們要保證在記憶體回收時,這些資源能得到清理。 48, Dispose方法應該允許被多次調用。
我們可以建立一個變數disposed來標明對象是否被釋放了。 49, 在Dispose模式中應提供一個受保護的虛方法。
因為這個類可能被其子類重寫,這時我們需要調用父類的Dispose方法。 50, 在Dispose模式中區別對待託管資源和非託管資源。
Dispose(true)是釋放所有的託管和非託管資源,是使用者主動調用的。Dispose(false),是記憶體回收行程調用的,只能釋放非託管資源,因為記憶體回收行程的調用順序是沒有保障的,如果這時去釋放託管資源,該託管資源可能已經被釋放了,從而引發異常。 51, 擁有本機資源或包含可釋放欄位的類型應該實現Dispose模式。 52, 及時釋放資源。
記憶體回收是根據一定的策略來執行的,不再使用的資源要及時釋放,不要等到記憶體回收。 53, 必要時應將對象的引用賦值為null。
但要注意一點,對局部變數或函數的參數賦值為null沒有太大的意義,因為這些變數會在函數結束時交給記憶體回收行程處理,物件變數不再持有對對象的引用。 54, 為無用欄位標註不可序列化。
原因可以是以下幾種:節約了空間;還原序列化後欄位沒有意義了,如Windows的控制代碼;業務上的原因不允許被序列化,如密碼;欄位本身對應的類型不可序列化,這會拋出SerializationException異常。在類型上使用Serializable特性可以將整個類型標識為可序列化,如果部分欄位不需要序列化,可以在該欄位上應用NonSerialized特性。屬性事實上是方法,所以是不能序列化的,自動屬性也是如此。另外,要標識事件為不可序列化,需要用field: NonSerialized文法。 55, 利用定製特性減少還原序列化的欄位。
OnDeSerializedAttribute,應用於方法時會在對象被還原序列化後調用,我們可以在這裡對沒有序列化的欄位進行必要的重建。相似的特性還有OnDeSerializingAttribute,OnSerializedAttribute,OnSerializingAttribute。 56, 繼承ISerializable介面實現更靈活的序列化過程。
上面提到的幾個特性如果不能完全滿足我們的要求,可以考慮實現這個介面。有了這個介面,上面應用的特性會被忽略掉。與這個介面相關的兩個方法是:ISerializable.GetObjectData,用於序列化對象。protected 類名(SerializationInfo info,StreamingContext context),添加這個受保護的建構函式用於還原序列化。注意,這個方法需要我們手動添加,編譯器檢查不到,否則不能還原序列化,你可以認為這是一種約定。 57, 實現ISerializable的子類應負責父類的序列化。
如果父類實現了ISerializable介面,我們調用base.GetObjectData就可以了;如果沒有實現,則需要對父類的每個欄位單獨進行序列化。 58, 用拋出異常來取代錯誤返回碼。
錯誤返回碼會導致判斷分支增加,給調用者帶來麻煩,應充分利用.net的異常處理機制來簡化代碼。 59, 不要在不恰當的場合引發異常。
對於調用WindowsAPI或第三方DLL只能返回錯誤碼的情況,可以考慮拋出自己的異常。代碼存在資源泄漏,資源不可用等可以拋出異常。在捕獲異常時,需要封裝更有用的資訊,可以拋出異常。正常的商務程序不應使用異常來處理,例如可控範圍內的輸入輸出。 60, 重新拋出異常時使用InnerException。
將內部異常放置到打包異常裡,以供外部程式瞭解到異常發生的真正原因。 61, 避免在finally塊中寫無效代碼。
如果catch塊中有return語句,return 語句是優先於finally塊的,也就是如果return返回了變數的值,finally塊再修改了這個變數的值,返回結果不會受到影響。當然返回引用的情況除外,因為兩個引用指向的是同一個對象。編譯器為我們新增加了一個變數用於儲存Return處的值,在finally塊結束後再返回這個值。另外,對於CSE(Corrupted State Exceptions)異常,在.net 4.0以後CLR不會捕獲了,也就是如果程式拋出如AccessViolationException的異常,catch塊和finally塊代碼不會被執行。注意,如果我們主動在Managed 程式碼中拋出的new AccessViolationException()是會被捕獲的,CLR會檢查異常的來源。要改變這種預設的行為,有以下幾種方法。1)在調用的方法上添加[System.Runtime.ExceptionServices.HandleProcessCorruptedStateExceptions]特性。2)改變工程屬性的.net版本為3.5或更早,再進行編譯。即使這個程式運行在.net4.0的環境中,也會捕獲CES異常。3)修改Config檔案的配置,在Configuration/runtime下添加這個節點:<runtime><legacyCorruptedStateExceptionsPolicy enabled="true"/></runtime>。後面兩種修改方法都是對整個工程生效的。 62, 避免無故的嵌套異常。
嵌套異常會使代碼變得臃腫,除非我們要恢複一些狀態,否則不應該每個函數都去try catch,這會給調用者帶來麻煩,同時也會隱藏掉異常出錯的真正原因,不利於排錯。我認為內建函式如何僅僅是為了寫日誌,應該讓最外層函數統一處理,內建函式不要捕獲異常,如果非要捕獲,也要用throw,而不能用throw e,後者會造成堆棧資訊的丟失。 63, 避免吃掉異常。
如果我們不知道如何處理異常,就應該往上拋出,交給上層代碼來處理。 64, 為迴圈添加Tester-Doer模式,而不是添加try-catch。
在大量失敗的測試中使用try-catch會大大損害效能。可以添加條件判斷(如IF等)來避免這種情況。 65, 總是捕獲未處理的異常。
未捕獲的異常,可以通過System.AppDomain.CurrentDomain.UnhandledException捕獲到。Winform中還有一個Application.ThreadException用於捕獲UI線程異常。WPF中是Dispatcher類的DispatcherUnhandledException,並且需要設定e.Handed=true;否則會繼續引發System.AppDomain.CurrentDomain.UnhandledException異常。 66, 正確捕獲多線程中的異常。
多線程中的異常不會主動傳遞到UI線程中,如果未捕獲就會傳遞到System.AppDomain.CurrentDomain.UnhandledException這裡。我們可以將線程中的異常通過代理的方式傳遞到UI線程中,this.BeginInvoke((Action)delegate { throw ex; });這樣可以對未處理的異常進行統一的處理。 67, 慎用自訂異常。
通常自訂異常的理由是:1)方便調試,通過自訂的異常,可以準確知道代碼所發生的事情。2)邏輯封裝,將多種異常封裝成一種異常,方便調用者編碼。3)引入新的異常類。 68, 從System.Exception或其他常見的基本異常中派生異常。
注意,自訂異常如需實現序列化,要繼承ISerilizable介面,將自訂欄位序列化後調用父類的序列化方法GetObjectData。 69, 使用finally避免資源泄漏。
Finally塊的特性使我們可以將釋放資源的操作放在這裡,因為即便出現異常,finally也會被執行。 70, 避免在調用棧較低的位置記錄異常。
並不是所有的異常都需要被記錄到日誌,一類情況是異常發生的情境需要被記錄,還有一類是未捕獲的異常。為了避免異常被重複記錄,應該在項目的初期就約定異常日誌記錄的規則。
【進階修鍊】——改善C#程式品質(4)