日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 >

C#规范整理·资源管理和序列化

發布時間:2023/12/4 27 豆豆
生活随笔 收集整理的這篇文章主要介紹了 C#规范整理·资源管理和序列化 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

源管理(尤其是內存回收)曾經是程序員的噩夢,不過在.NET平臺上這個噩夢似乎已經不復存在。CLR在后臺為垃圾回收做了很多事情,使得我們現在談起在.NET上進行開發時,都會說還是new一個對象吧!回收?有垃圾回收器呢。其實并沒有這么簡單。
??對象序列化是現代軟件開發中的一項重要技術,無論是本地存儲還是遠程傳輸,都會使用序列化技術來保持


資源管理


1.顯式釋放資源需繼承接口IDisposable

C#中的每一個類型都代表一種資源,而資源又分為兩類:

  • 托管資源 由CLR管理分配和釋放的資源,即從CLR里new出來的對象。

  • 非托管資源 不受CLR管理的對象,如Windows內核對象,或者文件、數據庫連接、套接字、COM對象等。

如果我們的類型使用到了非托管資源,或者需要顯式地釋放托管資源,那么就需要讓類型繼承接口IDisposable,這毫無例外。這相當于告訴調用者:類型對象是需要顯式釋放資源的,你需要調用類型的Dispose方法。,一個標準的繼承了IDisposable接口的類型應該像下面這樣去實現。這種實現我們稱為Dispose模式:

承IDispose接口也為實現語法糖using帶來了便利。在C#編碼中,如果像下面這樣使用using,編譯器會自動為我們生成調用Dispose方法的IL代碼:

2.即使提供了顯式釋放方法,也應該在終結器中提供隱式清理

在標準的Dispose模式中,我們注意到一個以~開頭的方法,如下所示:

Copy


這個方法叫做類型的終結器。提供終結器的意義在于:我們不能奢望類型的調用者肯定會主動調用Dispose方法,基于終結器會被垃圾回收器調用這個特點,它被用作資源釋放的補救措。

對于沒有繼承IDisposable接口的類型對象,垃圾回收器則會直接釋放對象所占用的內存;而對于實現了Dispose模式的類型,在每次創建對象的時候,CLR都會將該對象的一個指針放到終結列表中,垃圾回收器在回收該對象的內存前,會首先將終結列表中的指針放到一個freachable隊列中。同時,CLR還會分配專門的線程讀取freachable隊列,并調用對象的終結器,只有到這個時候,對象才會真正被標識為垃圾,并且在下一次進行垃圾回收時釋放對象占用的內存。

可以看到,實現了Dispose模式的類型對象,起碼要經過兩次垃圾回收才能真正地被回收掉,因為垃圾回收機制會首先安排CLR調用終結器。基于這個特點,如果我們的類型提供了顯式釋放的方法來減少一次垃圾回收,同時也可以在終結器中提供隱式清理,以避免調用者忘記調用該方法而帶來的資源泄漏。

注意1 在有的文檔中,終結器也稱做析構器。

注意2 如果調用者已經調用Dispose方法進行了顯式地資源釋放,那么,隱式釋放資源(也就是終結器)就沒有必要再運行了。
FCL中的類型GC提供了靜態方法SuppressFinalize來通知垃圾回收器這一點。注意查看Dispose方法:

Copypublic void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}

3.Dispose方法應允許被多次調用

一個類型的Dispose方法應該允許被多次調用而不拋異常。鑒于這個原因,類型內部維護了一個私有的布爾型變量disposed,如下所示:

在實際清理代碼的方法中,加入了如下的判斷語句:

if(disposed)
{
return;
}

在//省略部分的代碼,方法的最后為disposed賦值為true:disposed=true;這意味著如果類型已經被清理過一次,那么清理工作將不再進行。對象被調用過Dispose方法,并不表示該對象已經被置為null,且被垃圾回收機制回收過內存,已經徹底不存在了。事實上,對象的引用可能還在。但是,對象被Dispose過,說明對象的正常狀態已經不存在了,此時如果調用對象公開的方法,應該會為調用者拋出一個ObjectDisposedException。

4.在Dispose模式中應提取一個受保護的虛方法

真正實現IDisposable接口的Dispose方法并沒有做實際的清理工作,它其實是調用了下面這個帶布爾參數且受保護的虛方法:

Copy

之所以提供這樣一個受保護的虛方法,是因為考慮了這個類型會被其他類繼承的情況。如果類型存在一個子類,子類也許會實現自己的Dispose模式。受保護的虛方法用來提醒子類:必須在實現自己的清理方法時注意到父類的清理工作,即子類需要在自己的釋放方法中調用base.Dispose方法。

如果不為類型提供這個受保護的虛方法,很有可能讓開發者設計子類的時候忽略掉父類的清理工作。所以,基于繼承體系的原因,要為類型的Dispose模式提供一個受保護的虛方法。

5.在Dispose模式中應區別對待托管資源和非托管資源

Dispose模式設計的思路基于:如果調用者顯式調用了Dispose方法,那么類型就該按部就班地將自己的資源全部釋放。如果調用者忘記調用Dispose方法了,那么類型就假定自己的所有托管資源會全部交給垃圾回收器回收,所以不進行手工清理。理解了這一點,我們就理解了為什么在Dispose方法中,虛方法傳入的參數是true,而在終結器中,虛方法傳入的參數是false。

6.具有可釋放字段的類型或擁有本機資源的類型應該是可釋放的

我們將C#中的類型分為:普通類型和繼承了IDisposable接口的非普通類型。非普通類型除了那些包含托管資源的類型外,還包括類型本身也包含一個非普通類型的字段的類型。
在標準的Dispose模式中,我們對非普通類型舉了一個例子:一個非普通類型AnotherResource。由于AnotherResource是一個非普通類型,所以如果現在有這么一個類型,它組合了AnotherResource,那么它就應該繼承IDisposable接口,代碼如下所示:

類型AnotherSampleClass雖然沒有包含任何顯式的非托管資源,但是由于它本身包含了一個非普通類型,所以我們仍舊必須為它實現一個標準的Dispose模式。
除此以外,類型擁有本機資源(即非托管類型資源),它也應該繼承IDisposable接口。

7.及時釋放資源

很多人會注意到:垃圾回收機制自動為我們隱式地回收了資源(垃圾回收器會自動調用終結器),于是不禁會問:為什么還要主動釋放資源呢?我們來看以下這個例子:



如果連續兩次單擊打開文件按鈕,系統就會報錯,如下所示:

IOException:文件"c:\test.txt" 正由另一進程使用,因此該進程無法訪問此文件。

現在來分析:在打開文件的方法中,方法執行完畢后,由于局部變量fileStream在程序中已經沒有任何地方引用了,所以它會在下一次垃圾回收時被運行時標記為垃圾。那么,什么時候會進行下一次垃圾回收呢,或者說垃圾回收器什么時候才開始真正進行回收工作呢?微軟官方的解釋是,當滿足以下條件之一時將發生垃圾回收:

  • 系統具有低的物理內存。

  • 由托管堆上已分配的對象使用的內存超出了可接受的范圍。

  • 調用GC.Collect方法。幾乎在所有情況下,我們都不必調用此方法,因為垃圾回收器會負責調用它。

但在本實例中,為了體會一下不及時回收資源的危害,所以進行了一次GC.Collect方法的調用,大家可以仔細體會運行這個方法所帶來的不同。

垃圾回收機制中還有一個“代”的概念。一共分為3代:0代、1代、2代。第0代包含一些短期生存的對象,如示例代碼中的局部變量fileStream就是一個短期生存對象。當buttonOpen_Click退出時,fileStream就被丟到了第0代,但此刻并不進行垃圾回收,當第0代滿了的時候,運行時會認為現在低內存的條件已滿足,那時才會進行垃圾回收。所以,我們永遠不知道fileStream這個對象(或者說資源)什么時候才會被回收。在回收之前,它實際已經沒有用處,卻始終占據著內存(或者說資源)不放,這對應用系統來說是一種極大的浪費,并且,這種浪費還會干擾程序的正常運行(如在本實例中,由于它始終占著文件資源,導致我們不能再次使用這個文件資源了)。

不及時釋放資源還帶來另外一個問題。在上面中我們已經了解到,如果類型本身繼承了IDisposable接口,垃圾回收機制雖然會自動幫我們釋放資源,但是這個過程卻延長了,因為它不是在一次回收中完成所有的清理工作。本實例中的代碼因為fileStream繼承了IDisposable接口,故第一次進行垃圾回收的時候,垃圾回收器會調用fileStream的終結器,然后等待下一次的垃圾回收,這時fileStream對象才有可能被真正的回收掉。

了解了不及時釋放資源的危害后,現在來改進這個程序,如下所示:

Copy


這確實是一種改進,但是我們沒考慮到方法中的第一行代碼可能會拋出異常。如果它拋出異常,那么fileStream.Dispose()將永遠不會執行。于是,再一次改進,如下所示:



為了更進一步簡化語句,還可以使用語法糖“using”關鍵字。

8.必要時應將不再使用的對象引用賦值為null

在CLR托管的應用程序中,存在一個“根”的概念,類型的靜態字段、方法參數,以及局部變量都可以作為“根”存在(值類型不能作為“根”,只有引用類型的指針才能作為“根”)。
當檢查到方法內的“根”時,如果發現沒有任何一個地方引用了局部變量,則不管是否已經顯式將其賦值為null,都意味著該“根”已經被停止。然后,垃圾回收器會發現該根的引用為空,同時標記該根可被釋放。

需要注意一下幾點

  • 局部變量賦值為null無意義,因為編譯器在編譯時就會過濾。

  • 類型的靜態字段賦值為null是有意義的。是因為類型的靜態字段一旦被創建,該“根”就一直存在。所以,垃圾回收器始終不會認為它是一個垃圾。非靜態字段則不存在這個問題。

  • 在實際工作中,一旦我們感覺到自己的靜態引用類型參數占用的內存空間比較大,并且用完后不會再使用,便可以立刻將其賦值為null。這也許并不必要,但這絕對是一個好習慣。試想在一個系統中那些時不時在類型中出現的靜態變量吧!它們就那樣靜靜地待在內存里,一旦被創建,就永遠不會離開。或許我們可以專門為此寫一個小建議,那就是:盡量少用靜態變量。

    序列化

    1.為無用字段標注不可序列化

    序列化是指這樣一種技術:把對象轉變成流。相反的過程,我們稱為反序列化。在很多的場合都需要用到這項技術,例如:

    • 把對象保存到本地,在下次運行程序的時候,恢復這個對象。

    • 把對象傳到網絡中的另外一臺終端上,然后在此終端還原這個對象。

    • 其他的場合,如:把對象復制到系統的粘貼板中,然后用快捷鍵Ctrl+V恢復這個對象。

    有以下幾方面的原因,決定了要為無用字段標注不可序列化:

  • 節省空間。類型在序列化后往往會存儲到某個地方,如數據庫、硬盤或內存中,如果一個字段在反序列化后不需要保持狀態,那它就不應該被序列化,這會占用寶貴的空間資源。

  • 反序列化后字段信息已經沒有意義了。如Windows內核句柄,在反序列化后往往已經失去了意義,所以它就不應該被序列化。

  • 字段因為業務上的原因不允許被序列化。例如,明文密碼不應該被序列化后一同保存在文件中。

  • 如果字段本身所對應的類型在代碼中未被設定為可序列化,那它就該被標注不可序列化,否則運行時會拋出異常SerializationException。




  • 注意
    1.由于屬性本質上是方法,所以不能將NonSerialized特性應用于屬性上,在標識某個屬性不能被序列化時,自動實現的屬性顯然已經不能使用。
    2.要讓事件不能被序列化,需使用改進的特性語法field:NonSerialized。

    2.利用定制特性減少可序列化的字段

    特性(attribute)可以聲明式地為代碼中的目標元素添加注解。運行時可以通過查詢這些托管模塊中的元數據信息,達到改變目標元素運行時行為的目的。在System.Runtime.Serialization命名空間下,有4個這樣的特性,下面是MSDN上對它們的解釋:

    • OnDeserializedAttribute,當它應用于某方法時,會指定在對象反序列化后立即調用此方法。

    • OnDeserializingAttribute,當它應用于某方法時,會指定在反序列化對象時調用此方法。

    • OnSerializedAttribute,如果將對象圖應用于某方法,則應指定在序列化該對象圖后是否調用該方法。

    • OnSerializingAttribute,當它應用于某個方法時,會指定在對象序列化前調用此方法。

    示例:



    3.使用繼承ISerializable接口更靈活地控制序列化過程

    除了利用特性Serializable之外,我們還可以注意到在序列化的應用中,常常會出現一個接口ISerializable。接口ISerializable的意義在于,如果特性Serializable,以及與其相配套的OnDeserializedAttribute、OnDeserializingAttribute、OnSerializedAttribute、OnSerializingAttribute、NonSerialized等特性不能完全滿足自定義序列化的要求,那就需要繼承ISerializable了。
    例如我們要將一個對象反序列化成為另外一個對象,就要都實現ISerializable接口,原理其實很簡單,那就是在一個對象的GetObjectData方法中處理序列化,在另一個對象的受保護構造方法中反序列化。

    4.實現ISerializable的子類型應負責父類的序列化#

    我們將要實現的繼承自ISerializable的類型Employee有一個父類Person,假設Person沒有實現序列化,而現在子類Employee卻要求能夠滿足序列化的場景。不過很遺憾,序列化器沒有默認去處理Person類型對象,需要我們在子類中受保護的構造方法和GetObjectData方法,為它們加入父類字段的處理

    總結

    如有需要, 上一篇的《C#規范整理·泛型委托事件》也可以看看!

    原文地址:https://www.cnblogs.com/zhan520g/p/11061237.html

    .NET社區新聞,深度好文,歡迎訪問公眾號文章匯總?http://www.csharpkit.com?


    總結

    以上是生活随笔為你收集整理的C#规范整理·资源管理和序列化的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。