版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請(qǐng)進(jìn)行舉報(bào)或認(rèn)領(lǐng)
文檔簡(jiǎn)介
1、<p> .NET Resource Management</p><p> Bill Wagner</p><p> Effective C#: 50 Specific Ways to Improve Your C#,Chapter 2,.NET Resource Management,Bill Wagner,Addison Wesley Profes
2、sional,2004,77~115</p><p><b> .NET 資源管理</b></p><p><b> 比爾·瓦格拉</b></p><p> Effective C# 中文版改善C#程序的50種方法,第二章,.NET 資源管理,比爾·瓦格拉,2004,77~115</p>
3、;<p> 一個(gè)簡(jiǎn)單的事實(shí):.Net應(yīng)用程序是在一個(gè)托管的環(huán)境里運(yùn)行的,這個(gè)環(huán)境和不同的設(shè)計(jì)器有很大的沖突,這就才有了Effective C#。極大限度上的討論這個(gè)環(huán)境的好處,須要把你對(duì)本地化環(huán)境的想法改變?yōu)?Net CLR。也就意味著要明白.Net的垃圾回收器。在你明白這一章里所推薦的內(nèi)容時(shí),有必要對(duì).Net的內(nèi)存管理環(huán)境有個(gè)大概的了解。那我們就開始大概的了解一下吧。</p><p> 垃圾回
4、收器(GC)為你控制托管內(nèi)存。不像本地運(yùn)行環(huán)境,你不用負(fù)責(zé)對(duì)內(nèi)存泄漏,不定指針,未初始化指針,或者一個(gè)其它內(nèi)存管理的服務(wù)問題。但垃圾回收器前不是一個(gè)神話:你一樣要自己清理。你要對(duì)非托管資源負(fù)責(zé),例如文件句柄,數(shù)據(jù)鏈接,GDI+對(duì)象,COM對(duì)象,以及其它一些系統(tǒng)對(duì)象。</p><p> 這有一個(gè)好消息:因?yàn)镚C管理內(nèi)存,明確的設(shè)計(jì)風(fēng)格可以更容易的實(shí)現(xiàn)。循環(huán)引用,不管是簡(jiǎn)單關(guān)系還是復(fù)雜的網(wǎng)頁(yè)對(duì)象,都非常容易。GC的
5、標(biāo)記以及嚴(yán)謹(jǐn)?shù)母咝惴梢詸z測(cè)到這些關(guān)系,并且完全的刪除不可達(dá)的網(wǎng)頁(yè)對(duì)象。GC是通過對(duì)從應(yīng)用程序的根對(duì)象開始,通過樹形結(jié)構(gòu)的“漫游”來(lái)斷定一個(gè)對(duì)象是否可達(dá)的,而不是強(qiáng)迫每個(gè)對(duì)象都保持一些引用跟蹤,COM就是這樣的。DataSet就是一個(gè)很好的例子,展示了這樣的算法是如何簡(jiǎn)化并決定對(duì)象的所屬關(guān)系的。DataSet是一個(gè)DataTable的集合,而每一個(gè)DataTable又是DataRow的集合,每一個(gè)DataRow又是DataItem的集
6、合,DataColum定義了這些類型的關(guān)系。這里就有一些從DataItem到它的列的引用。而同時(shí),DataTime也同樣有一個(gè)引用到它的容器上,也就是DataRow。DataRow包含引用到DataTable,最后每個(gè)對(duì)象都包含一個(gè)引用到DataSet。</p><p> 如果這還不夠復(fù)雜,那可以創(chuàng)建一個(gè)DataView,它提供對(duì)經(jīng)過過濾后的數(shù)據(jù)表的順序訪問。這些都是由DataViewManager管理的。所有
7、這些貫穿網(wǎng)頁(yè)的引用構(gòu)成了DataSet。釋放內(nèi)存是GC的責(zé)任。因?yàn)?Net框架的設(shè)計(jì)者讓你不必釋放這些對(duì)象,這些復(fù)雜的網(wǎng)頁(yè)對(duì)象引用不會(huì)造成問題。沒有必須關(guān)心這些網(wǎng)頁(yè)對(duì)象的合適的釋放順序,這是GC的工作。GC的設(shè)計(jì)結(jié)構(gòu)可以簡(jiǎn)化這些問題,它可以識(shí)別這些網(wǎng)頁(yè)對(duì)象就是垃圾。在應(yīng)用程序結(jié)束了對(duì)DataSet的引用后,沒有人可以引用到它的子對(duì)象了(譯注:就是DataSet里的對(duì)象再也引用不到了)。因此,網(wǎng)頁(yè)里還有沒有對(duì)象循環(huán)引用DataSet,Da
8、taTables已經(jīng)一點(diǎn)也不重要了,因?yàn)檫@些對(duì)象在應(yīng)用程序都已經(jīng)不能被訪問到了,它們是垃圾了。</p><p> 垃圾回收器在它獨(dú)立的線程上運(yùn)行,用來(lái)從你的程序里移除不使用的內(nèi)存。而且在每次運(yùn)行時(shí),它還會(huì)壓縮托管堆。壓縮堆就是把托管堆中活動(dòng)的對(duì)象移到一起,這樣就可以空出連續(xù)的內(nèi)存。圖2.1展示了兩個(gè)沒有進(jìn)行垃圾回收時(shí)的內(nèi)存快照。所有的空閑內(nèi)存會(huì)在垃圾回收進(jìn)行后連續(xù)起來(lái)。</p><p>
9、 圖2.1 垃圾回收器不僅僅是移動(dòng)不使用的內(nèi)存,還移除動(dòng)其它的對(duì)象,從而壓縮使用的內(nèi)存,讓出最多的空閑內(nèi)存。 </p><p> 正如你剛開始了解的,垃圾回收器的全部責(zé)任就是內(nèi)存管理。但,所有的系統(tǒng)資源都是你自己負(fù)責(zé)的。你可以通過給自己的類型定義一個(gè)析構(gòu)函數(shù),來(lái)保證釋放一些系統(tǒng)資源。析構(gòu)函數(shù)是在垃圾回收器把對(duì)象從內(nèi)存移除前,由系統(tǒng)調(diào)用的。你可以,也必須這樣來(lái)釋放任何你所占用的非托管資源。對(duì)象的析構(gòu)函數(shù)有時(shí)是在
10、對(duì)象成為垃圾之后調(diào)用的,但是在內(nèi)存歸還之前。這個(gè)非確定的析構(gòu)函數(shù)意味著在你無(wú)法控制對(duì)象析構(gòu)與停止使用之間的關(guān)系(譯注:對(duì)象的析構(gòu)與對(duì)象的無(wú)法引用是兩個(gè)完全不同的概念。關(guān)于GC,本人推薦讀者參考一下Jeffrey的".Net框架程序設(shè)計(jì)(修訂版)"中討論的垃圾回收器)。對(duì)C++來(lái)說這是個(gè)重大的改變,并且這在設(shè)計(jì)上有一個(gè)重大的分歧。有經(jīng)驗(yàn)的C++程序員寫的類總在構(gòu)造函數(shù)內(nèi)申請(qǐng)內(nèi)存并且在析構(gòu)函數(shù)中釋放它們:</p&g
11、t;<p> // 好的 C++, 壞的C#:</p><p> class CriticalSection</p><p><b> {</b></p><p><b> public:</b></p><p> // 構(gòu)造系統(tǒng)需要的資源</p><p&
12、gt; CriticalSection( )</p><p><b> {</b></p><p> EnterCriticalSection( );</p><p><b> }</b></p><p><b> // 銷毀資源</b></p><
13、;p> ~CriticalSection( )</p><p><b> {</b></p><p> ExitCriticalSection( );</p><p><b> }</b></p><p><b> };</b></p><p
14、><b> // 使用:</b></p><p> void Func( )</p><p><b> {</b></p><p> // 系統(tǒng)資源的生存周期</p><p> CriticalSection s;</p><p> // Do work.&
15、lt;/p><p><b> //...</b></p><p> // compiler generates call to destructor.</p><p> // code exits critical section.</p><p><b> }</b></p>&
16、lt;p> 這是一種很常見的C++風(fēng)格,它保證資源無(wú)異常的釋放。但這在C#里不工作,至少,與這不同。明確的析構(gòu)函數(shù)不是.Net環(huán)境或者C#的一部份。強(qiáng)行用C++的風(fēng)格在C#里使用析構(gòu)函數(shù)不會(huì)讓它正常的工作。在C#里,析構(gòu)函數(shù)確實(shí)是正確的運(yùn)行了,但它不是即時(shí)運(yùn)行的。在前面那個(gè)例子里,代碼最終在critical section上,但在C#里,當(dāng)析構(gòu)函數(shù)存在時(shí),它并不是在critical section上。它會(huì)在后面的某個(gè)未知時(shí)間上運(yùn)
17、行。你不知道是什么時(shí)候,你也無(wú)法知道是什么時(shí)候。</p><p> 依懶于析構(gòu)函數(shù)同樣會(huì)導(dǎo)致性能上的損失。須要析構(gòu)的對(duì)象在垃圾回收器上放置了一劑性能毒藥。當(dāng)GC發(fā)現(xiàn)某個(gè)對(duì)象是垃圾但是須要析構(gòu)時(shí),它還不能直接從內(nèi)存上刪除這個(gè)對(duì)象。首先,它要調(diào)用析構(gòu)函數(shù),但析構(gòu)函數(shù)的調(diào)用不是在垃圾回收器的同一個(gè)線程上運(yùn)行的。取而代之的是,GC不得不把對(duì)象放置到析構(gòu)隊(duì)列中,讓另一個(gè)線程讓執(zhí)行所有的析構(gòu)函數(shù)。GC繼續(xù)它自己的工作,從內(nèi)
18、存上移除其它的垃圾。在下一個(gè)GC回收時(shí),那些被析構(gòu)了的對(duì)象才會(huì)再?gòu)膬?nèi)存上移除。圖2.2展示了三個(gè)內(nèi)存使用不同的GC情況。注意,那些須要析構(gòu)的對(duì)象會(huì)待在內(nèi)存里,直到下一次GC回收。</p><p> 圖2.2 這個(gè)順序展示了析構(gòu)函數(shù)在垃圾回收器上起的作用。對(duì)象會(huì)在內(nèi)存里存在的時(shí)間更長(zhǎng),須要啟動(dòng)另一個(gè)線程來(lái)運(yùn)行垃圾回收器。</p><p> 這用使你相信:那些須要析構(gòu)的對(duì)象在內(nèi)存至少多生存
19、一個(gè)GC回收循環(huán)。但,我是簡(jiǎn)化了這些事。實(shí)際上,因?yàn)榱硪粋€(gè)GC的介入(譯注:其實(shí)只有一個(gè)GC,作者是想引用回收代的問題。),使得情況比這復(fù)雜得多。.Net回收器采用”代“來(lái)優(yōu)化這個(gè)問題。代可以幫助GC來(lái)很快的標(biāo)識(shí)那些看上去看是垃圾的對(duì)象。所以從上一次回后開始創(chuàng)建的對(duì)象稱為第0代對(duì)象,所有那些經(jīng)過一次GC回收后還存在的對(duì)象稱為第1代對(duì)象。所有那些經(jīng)過2次或者2次以上GC回收后還存在的對(duì)象稱為第2代對(duì)象。</p><p&
20、gt; 分代的目的就是用來(lái)區(qū)分臨時(shí)變量以及一些應(yīng)用程序的全局變量。第0代對(duì)象很可能是臨時(shí)的變量。成員變量,以及一些全局變量很快會(huì)成為第1代對(duì)象,最終成為第2代對(duì)象。</p><p> GC通過限制檢測(cè)第1以及第2代對(duì)象來(lái)優(yōu)化它的工作。每個(gè)GC循環(huán)都檢測(cè)第0代對(duì)象。粗略假設(shè)個(gè)GC會(huì)超過10次檢測(cè)來(lái)檢測(cè)第0代對(duì)象,而要超過100次來(lái)檢測(cè)所有對(duì)象。再次考慮析構(gòu)函數(shù)的開銷:一個(gè)須要析構(gòu)函數(shù)的對(duì)象可能要比一個(gè)不用析構(gòu)函
21、數(shù)的對(duì)象在內(nèi)存里多待上9個(gè)GC回收循環(huán)。如果它還沒有被析構(gòu),它將會(huì)移到第2代對(duì)象。在第2代對(duì)象中,一個(gè)可以生存上100個(gè)GC循環(huán)直到下一個(gè)第2代集合。</p><p> 結(jié)束時(shí),記得一個(gè)垃圾回收器負(fù)責(zé)內(nèi)存管理的托管環(huán)境的最大好處:內(nèi)存泄漏,其它指針的服務(wù)問題不在是你的問題。非內(nèi)存資源迫使你要使用析構(gòu)函數(shù)來(lái)確保清理非內(nèi)存資源。析構(gòu)函數(shù)會(huì)對(duì)你的應(yīng)用程序性能產(chǎn)生一些影響,但你必須使用它們來(lái)防止資源泄漏(譯注:請(qǐng)注意理
22、解非內(nèi)存資源是什么,一般是指文件句柄,網(wǎng)絡(luò)資源,或者其它不能在內(nèi)存中存放的資源)。通過實(shí)現(xiàn)IDisposable接口來(lái)避免析構(gòu)函數(shù)在垃圾回收器上造成的性能損失。接下來(lái)的具體的原則將會(huì)幫助你更有效的使用環(huán)境來(lái)開發(fā)程序。</p><p> Effective C# 原則12:選擇變量初始化而不是賦值語(yǔ)句</p><p> 一些類經(jīng)常不只一個(gè)構(gòu)造函數(shù)。時(shí)間一長(zhǎng),就難得讓它的成員變量以及構(gòu)造函
23、數(shù)進(jìn)行同步了。最好的確保這樣的事不會(huì)發(fā)生的方法就是:在聲明就是的時(shí)間就直接初始化,而不是在每個(gè)構(gòu)造函數(shù)內(nèi)進(jìn)行賦值。而且你應(yīng)該使用初始化器語(yǔ)法同時(shí)為靜態(tài)的和實(shí)例的變量進(jìn)行初始化。</p><p> 在C#里,當(dāng)你聲明一個(gè)變量時(shí)就自然的構(gòu)造了這個(gè)成員變量。直接賦值:</p><p> public class MyClass{ // declare the collecti
24、on, and initialize it. private ArrayList _coll = new ArrayList( );}</p><p> 忽略你最終會(huì)給MyClass添加多少個(gè)構(gòu)造函數(shù),_coll會(huì)正確的初始化。編譯器會(huì)產(chǎn)生一些代碼,使得在你的任何一個(gè)構(gòu)造函數(shù)調(diào)用前,都會(huì)初始化你聲明的實(shí)例變量。當(dāng)你添加一個(gè)新的構(gòu)造函數(shù)時(shí),_coll就給你初始化了。當(dāng)你添加了一個(gè)新的變量,你不用在
25、所有的構(gòu)造函數(shù)里添加初始化代碼;直接在聲明的地方對(duì)它進(jìn)行初始化就行了。同樣重要的是:如果你沒有明確的聲明任何一個(gè)構(gòu)造函數(shù),編譯會(huì)默認(rèn)的給你添加一個(gè),并且把所有的變量初始化過程都添加到這個(gè)構(gòu)造函數(shù)里。</p><p> 初始化器更像是一個(gè)到構(gòu)造函數(shù)的方便的快捷方法。初始化生成的代碼會(huì)放置在類型的構(gòu)造函數(shù)之前。初始化會(huì)在執(zhí)行類型的基類的構(gòu)造函數(shù)之前被執(zhí)行,并且它們是按你聲明的先后關(guān)系順序執(zhí)行的。</p>
26、<p> 使用初始化器是一個(gè)最簡(jiǎn)單的方法,在你的類型里來(lái)避免使用一些沒有賦值的變量,但這并不是很好。下面三種情況下,你不應(yīng)該使用初始化器語(yǔ)法。首先就是,如果你是初始化一個(gè)對(duì)象為0,或者為null。系統(tǒng)默認(rèn)會(huì)在你任何代碼執(zhí)行前,為所有的內(nèi)容都初始化為0。系統(tǒng)置0的初始化是基于底層的CPU指令,對(duì)整個(gè)內(nèi)存塊設(shè)置。你的任何其它置0的初始化語(yǔ)句是多余的。C#編譯器忠實(shí)的添加額外的指令把內(nèi)存設(shè)置為0。這并沒有錯(cuò),只是效率不高。事實(shí)
27、上,如果是處理值類型數(shù)據(jù),這是很不值的:</p><p> MyValType _MyVal1; // initialized to 0MyValType _MyVal2 = new MyValType(); // also 0</p><p> 兩條語(yǔ)句都是把變量置為0。第一個(gè)是通過設(shè)置包含_MyVal1的內(nèi)存來(lái)置0;而第二個(gè)是通過IL指令initobj,這對(duì)變量_My
28、Val2會(huì)產(chǎn)生裝箱與拆箱操作。這很要花一點(diǎn)額外的時(shí)間(參見原則17)。</p><p> 第二個(gè)低效率的是在你為一個(gè)對(duì)象添加兩個(gè)構(gòu)造函數(shù)時(shí)會(huì)產(chǎn)生。你使用初始化器初始化變量,而所有的構(gòu)造函數(shù)也對(duì)這些變量進(jìn)行了初始化。這個(gè)版本的MyClass兩個(gè)不同的ArrayList對(duì)象在它的構(gòu)造函數(shù)內(nèi):</p><p> public class MyClass{ // declare
29、 the collection, and initialize it. private ArrayList _coll = new ArrayList( );</p><p> MyClass( ) { }</p><p> MyClass( int size ) { _coll = new
30、ArrayList( size ); }}</p><p> 當(dāng)你創(chuàng)建一個(gè)新的MyClass對(duì)象時(shí),特別指定集合的大小,你創(chuàng)建了兩個(gè)數(shù)組列表。其中一個(gè)很快成為垃圾對(duì)象。初始化器在所有的構(gòu)造函數(shù)之前會(huì)執(zhí)行,構(gòu)造函數(shù)會(huì)創(chuàng)建第2個(gè)數(shù)組列表。編譯器產(chǎn)生了這個(gè)的一個(gè)版本,當(dāng)然這是你決不會(huì)手動(dòng)寫出來(lái)的。</p><p> public class MyClass{
31、// declare the collection, and initialize it. private ArrayList _coll;</p><p> MyClass( ) { _coll = new ArrayList( ); }</p><p> MyClass( int size )
32、0; { _coll = new ArrayList( ); _coll = new ArrayList( size ); }}</p><p> 最后一個(gè)原因要把初始化放到構(gòu)造函數(shù)里就是促使異常的捕獲。你不能在初始化器中使用try塊,任何在構(gòu)造時(shí)因成員變量產(chǎn)生的異??赡苎苌綄?duì)象的外面。你無(wú)法試圖在你的類里來(lái)捕獲它。你
33、應(yīng)該把那些初始化代碼移到構(gòu)造函數(shù)里,這樣你就可以捕獲異常從而保證你的代碼很友好(參見原則45)。</p><p> 變量初始化器是一個(gè)最簡(jiǎn)單的方法,在忽略構(gòu)造函數(shù)時(shí)來(lái)保證成員變量被正確的初始化。初始化器在所有的構(gòu)造函數(shù)之前被執(zhí)行。使用這樣的語(yǔ)法意味著當(dāng)你在為后來(lái)發(fā)布的版本中添加了構(gòu)造函數(shù)時(shí),不會(huì)忘記添加恰當(dāng)?shù)某跏蓟綐?gòu)造函數(shù)里。當(dāng)構(gòu)造函數(shù)與初始化生成同樣的成員對(duì)象時(shí),就使用初始化器。閱讀簡(jiǎn)單而且易于維護(hù)。<
34、;/p><p> Effective C# 原則13:用靜態(tài)構(gòu)造函數(shù)初始化類的靜態(tài)成員</p><p> 你應(yīng)該知道,在一個(gè)類型的任何實(shí)例初始化以前,你應(yīng)該初始化它的靜態(tài)成員變量。在里C#你可以使用靜態(tài)的預(yù)置方法和靜態(tài)構(gòu)造函數(shù)來(lái)實(shí)現(xiàn)這個(gè)目的。一個(gè)類的靜態(tài)構(gòu)造函數(shù)是一個(gè)與眾不同的,它在所有的方法,變量或者屬性訪問前被執(zhí)行。你可以用這個(gè)函數(shù)來(lái)初始化靜態(tài)成員變量,強(qiáng)制使用單件模式,或者實(shí)現(xiàn)其它任
35、何在類型的實(shí)例可用前應(yīng)該完成的工作。你不能用任何的實(shí)例構(gòu)造函數(shù),其它特殊的私有函數(shù), 或者任何其它習(xí)慣方法來(lái)初始化一個(gè)變量。</p><p> 和實(shí)例的預(yù)置方法一樣,你可以把靜態(tài)的預(yù)置方法做為靜態(tài)構(gòu)造函數(shù)可替代的選擇。如果須要簡(jiǎn)單的分配一個(gè)靜態(tài)成員,就直接使用初始化語(yǔ)法。當(dāng)你有更復(fù)雜的邏輯來(lái)初始化靜態(tài)成員變量時(shí),就創(chuàng)建一個(gè)靜態(tài)構(gòu)造函數(shù):</p><p> public class My
36、Singleton{ private static readonly MySingleton _theOneAndOnly = new MySingleton( );</p><p> public static MySingleton TheOnly { get {
37、 return _theOneAndOnly; } }</p><p> private MySingleton( ) { }</p><p> // remainder elided}</p><p> 可以用下面的方
38、法簡(jiǎn)單的實(shí)現(xiàn)單件模式,實(shí)際上你在初始化一個(gè)單件模式時(shí)可能有更復(fù)雜的邏輯:</p><p> public class MySingleton{ private static readonly MySingleton _theOneAndOnly;</p><p> static MySingleton( ) { _th
39、eOneAndOnly = new MySingleton( ); }</p><p> public static MySingleton TheOnly { get { return _theOneAndOnly;
40、160; } }</p><p> private MySingleton( ) { }</p><p> // remainder elided}</p><p> 同樣,和實(shí)例的預(yù)置方法一樣,靜態(tài)的預(yù)置方法在靜態(tài)的構(gòu)造函數(shù)調(diào)用前執(zhí)行。并且,你的靜態(tài)預(yù)置方法在基類的靜態(tài)構(gòu)造函數(shù)執(zhí)行前被執(zhí)行。</p>
41、<p> 當(dāng)應(yīng)用程序第一次裝載你的數(shù)據(jù)類型時(shí),CLR自動(dòng)調(diào)用靜態(tài)構(gòu)造函數(shù)。你只能定義一個(gè)靜態(tài)構(gòu)造函數(shù),并且不能有參數(shù)。因?yàn)殪o態(tài)構(gòu)造函數(shù)是CLR調(diào)用的,你必須十分注意異常的產(chǎn)生。如果在靜態(tài)構(gòu)造函數(shù)里產(chǎn)生了異常,CLR將會(huì)直接終止你的應(yīng)用程序。正因?yàn)楫惓#o態(tài)構(gòu)造函數(shù)常常代替靜態(tài)預(yù)置方法。如果你使用靜態(tài)預(yù)置方法,你自己不能捕獲異常。做為一個(gè)靜態(tài)的構(gòu)造,你可以這樣(參見原則45):</p><p> st
42、atic MySingleton( ){ try { _theOneAndOnly = new MySingleton( ); } catch { // Attempt recovery here. }}</p><p> 靜態(tài)預(yù)置方法和靜態(tài)構(gòu)造函數(shù)為你的類提供了最清爽的方法來(lái)
43、初始化靜態(tài)成員。與其它語(yǔ)言不同,它們被添加到C#語(yǔ)言中,是初始化靜態(tài)成員的兩個(gè)不同的特殊位置。</p><p> The simple fact that .NET programs run in a managed environment has a big impact on the kinds of designs that create effective C#. Taking utmost advan
44、tage of that environment requires changing your thinking from native environments to the .NET CLR. It means understanding the .NET Garbage Collector. An overview of the .NET memory management environment is necessary to
45、understand the specific recommendations in this chapter, so let's get on with the overview.</p><p> The Garbage Collector (GC) controls managed memory for you. Unlike native environments, you are not re
46、sponsible for memory leaks, dangling pointers, uninitialized pointers, or a host of other memory-management issues. But the Garbage Collector is not magic: You need to clean up after yourself, too. You are responsible fo
47、r unmanaged resources such as file handles, database connections, GDI+ objects, COM objects, and other system objects.</p><p> Here's the good news: Because the GC controls memory, certain design idioms
48、 are much easier to implement. Circular references, both simple relationships and complex webs of objects, are much easier. The GC's Mark and Compact algorithm efficiently detects these relationships and removes unre
49、achable webs of objects in their entirety. The GC determines whether an object is reachable by walking the object tree from the application's root object instead of forcing each object to keep track of referen</p&
50、gt;<p> If that's not complicated enough, you can create DataViews that provide access to filtered sequences of a data table. Those are all managed by a DataViewManager. There are references all through the w
51、eb of objects that make up a DataSet. Releasing memory is the GC's responsibility. Because the .NET Framework designers did not need to free these objects, the complicated web of object references did not pose a prob
52、lem. No decision needed to be made regarding the proper sequence of freeing this web</p><p> The Garbage Collector runs in its own thread to remove unused memory from your program. It also compacts the mana
53、ged heap each time it runs. Compacting the heap moves each live object in the managed heap so that the free space is located in one contiguous block of memory. Figure 2.1 shows two snapshots of the heap before and after
54、a garbage collection. All free memory is placed in one contiguous block after each GC operation.</p><p> Figure 2.1. The Garbage Collector not only removes unused memory, but it moves other objects in memor
55、y to compact used memory and maximize free space.</p><p> As you've just learned, memory management is completely the responsibility of the Garbage Collector. All other system resources are your respons
56、ibility. You can guarantee that you free other system resources by defining a finalizer in your type. Finalizers are called by the system before an object that is garbage is removed from memory. You canand mustuse these
57、methods to release any unmanaged resources that an object owns. The finalizer for an object is called at some time after it becomes garb</p><p> // Good C++, bad C#:</p><p> class CriticalSect
58、ion</p><p><b> {</b></p><p><b> public:</b></p><p> // Constructor acquires the system resource.</p><p> CriticalSection( )</p><p
59、><b> {</b></p><p> EnterCriticalSection( );</p><p><b> }</b></p><p> // Destructor releases system resource.</p><p> ~CriticalSection( )
60、</p><p><b> {</b></p><p> ExitCriticalSection( );</p><p><b> }</b></p><p><b> };</b></p><p><b> // usage:<
61、;/b></p><p> void Func( )</p><p><b> {</b></p><p> // The lifetime of s controls access to</p><p> // the system resource.</p><p> Crit
62、icalSection s;</p><p> // Do work.</p><p><b> //...</b></p><p> // compiler generates call to destructor.</p><p> // code exits critical section.</p&
63、gt;<p><b> }</b></p><p> This common C++ idiom ensures that resource deallocation is exception-proof. This doesn't work in C#, howeverat least, not in the same way. Deterministic fin
64、alization is not part of the .NET environment or the C# language. Trying to force the C++ idiom of deterministic finalization into the C# language won't work well. In C#, the finalizer eventually executes, but it doe
65、sn't execute in a timely fashion. In the previous example, the code eventually exits the critical section, but, in C#, it doesn</p><p> Relying on finalizers also introducesperformance penalties. Object
66、s that require finalization put a performance drag on the Garbage Collector. When the GC finds that an object is garbage but also requires finalization, it cannot remove that item from memory just yet. First, it calls th
67、e finalizer. Finalizers are not executed by the same thread that collects garbage. Instead, the GC places each object that is ready for finalization in a queue and spawns yet another thread to execute all the final</p
68、><p> Figure 2.2. This sequence shows the effect of finalizers on the Garbage Collector. Objects stay in memory longer, and an extra thread needs to be spawned to run the Garbage Collector.</p><p>
69、; This might lead you to believe that an object that requires finalization lives in memory for one GC cycle more than necessary. But I simplified things. It's more complicated than that because of another GC design
70、decision. The .NET Garbage Collector defines generations to optimize its work. Generations help the GC identify the likeliest garbage candidates more quickly. Any object created since the last garbage collection operatio
71、n is a generation 0 object. Any object that has survived one GC ope</p><p> The GC optimizes its work by limiting how often it examines first- and second-generation objects. Every GC cycle examines generati
72、on 0 objects. Roughly 1 GC out of 10 examines the generation 0 and 1 objects. Roughly 1 GC cycle out of 100 examines all objects. Think about finalization and its cost again: An object that requires finalization might st
73、ay in memory for nine GC cycles more than it would if it did not require finalization. If it still has not been finalized, it moves to generation 2. I</p><p> To close, remember that a managed environment,
74、where the Garbage Collector takes the responsibility for memory management, is a big plus: Memory leaks and a host of other pointer-related problems are no longer your problem. Nonmemory resources force you to create fin
75、alizers to ensure proper cleanup of those nonmemory resources. Finalizers can have a serious impact on the performance of your program, but you must write them to avoid resource leaks. Implementing and using the IDisposa
76、ble interface</p><p> Item 12: Prefer Variable Initializers to Assignment Statements</p><p> Classes often have more than one constructor. Over time, it's easy for the member variables and
77、 the constructors to get out of synch. The best way to make sure this doesn't happen is to initialize variables where you declare them instead of in the body of every constructor. You should utilize the initializer s
78、yntax for both static and instance variables.</p><p> Constructing member variables when you declare that variable is natural in C#. Just assign a value:</p><p> public class MyClass</p>
79、<p><b> {</b></p><p> // declare the collection, and initialize it.</p><p> private ArrayList _coll = new ArrayList( );</p><p><b> }</b></p>
80、<p> Regardless of the number of constructors you eventually addto the MyClass type, _coll will be initialized properly. The compiler generates code at the beginning of each constructor to execute all the initial
81、izers you have defined for your instance member variables. When you add a new constructor, _coll gets initialized. Similarly, if you add a new member variable, you do not need to add initialization code to every construc
82、tor; initializing the variable where you define it is sufficient. Equally</p><p> Initializers are more than a convenient shortcut for statements in a constructor body. The statements generated by initializ
83、ers are placed in object code before the body of your constructors. Initializers execute before the base class constructor for your type executes, and they are executed in the order the variables are declared in your cla
84、ss.</p><p> Using initializers is the simplest way to avoid uninitialized variables in your types, but it's not perfect. In three cases, you should not use the initializer syntax. The first is when you
85、are initializing the object to 0, or null. The default system initialization sets everything to 0 for you before any of your code executes. The system-generated 0 initialization is done at a very low level using the CPU
86、instructions to set the entire block of memory to 0. Any extra 0 initialization on your pa</p><p> MyValType _MyVal1; // initialized to 0</p><p> MyValType _MyVal2 = new MyValType(); // also
87、0</p><p> Both statements initialize the variable to all 0s. The first does so by setting the memory containing MyVal1 to 0. The second uses the IL instruction initobj, which causes both a box and an unbox
88、operation on the _MyVal2 variable. This takes quite a bit of extra time (see Item 17).</p><p> The second inefficiency comes when you create multiple initializations for the same object. You should use the
89、initializer syntax only for variables that receive the same initialization in all constructors. This version of MyClass has a path that creates two different ArrayList objects as part of its construction:</p><
90、p> public class MyClass</p><p><b> {</b></p><p> // declare the collection, and initialize it.</p><p> private ArrayList _coll = new ArrayList( );</p><
91、p> MyClass( )</p><p><b> {</b></p><p><b> }</b></p><p> MyClass( int size )</p><p><b> {</b></p><p> _coll = n
92、ew ArrayList( size );</p><p><b> }</b></p><p><b> }</b></p><p> When you create a new MyClass, specifying the size of the collection, you create two array
93、 lists. One is immediately garbage. The variable initializer executes before every constructor. The constructor body creates the second array list. The compiler creates this version of MyClass, which you would never code
94、 by hand. (For the proper way to handle this situation, see Item 14.)</p><p> public class MyClass</p><p><b> {</b></p><p> // declare the collection, and initialize
95、it.</p><p> private ArrayList _coll;</p><p> MyClass( )</p><p><b> {</b></p><p> _coll = new ArrayList( );</p><p><b> }</b><
96、;/p><p> MyClass( int size )</p><p><b> {</b></p><p> _coll = new ArrayList( );</p><p> _coll = new ArrayList( size );</p><p><b> }<
97、/b></p><p><b> }</b></p><p> The final reason to move initialization into the body of a constructor is to facilitate exception handling. You cannot wrap the initializers in a TR
98、y block. Any exceptions that might be generated during the construction of your member variables get propagated outside of your object. You cannot attempt any recovery inside your class. You should move that initializati
99、on code into the body of your constructors so that you implement the proper recovery code to create your type and gracefully handle </p><p> Variable initializers are the simplest way to ensure that the mem
100、ber variables in your type are initialized regardless of which constructor is called. The initializers are executed before each constructor you make for your type. Using this syntax means that you cannot forget to add th
101、e proper initialization when you add new constructors for a future release. Use initializers when all constructors create the member variable the same way; it's simpler to read and easier to maintain.</p><
102、p> Item 13: Initialize Static Class Members with Static Constructors</p><p> You know that you should initialize static member variables in a type before you create any instances of that type. C# lets y
103、ou use static initializers and a static constructor for this purpose. A static constructor is a special function that executes before any other methods, variables, or properties defined in that class are accessed. You us
104、e this function to initialize static variables, enforce the singleton pattern, or perform any other necessary work before a class is usable. You should not</p><p> As with instance initialization, you can u
105、se the initializer syntax as an alternative to the static constructor. If you simply need to allocate a static member, use the initializer syntax. When you have more complicated logic to initialize static member variable
106、s, create a static constructor.</p><p> Implementing the singleton pattern in C# is the most frequent use of a static constructor. Make your instance constructor private, and add an initializer:</p>
107、<p> public class MySingleton</p><p><b> {</b></p><p> private static readonly MySingleton _theOneAndOnly =</p><p> new MySingleton( );</p><p> pu
溫馨提示
- 1. 本站所有資源如無(wú)特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請(qǐng)下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請(qǐng)聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁(yè)內(nèi)容里面會(huì)有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
- 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 眾賞文庫(kù)僅提供信息存儲(chǔ)空間,僅對(duì)用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對(duì)用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對(duì)任何下載內(nèi)容負(fù)責(zé)。
- 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請(qǐng)與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對(duì)自己和他人造成任何形式的傷害或損失。
最新文檔
- .net 資源管理[外文翻譯]
- 混凝土相關(guān)外文翻譯
- php相關(guān)外文翻譯
- 企業(yè)資源計(jì)劃(erp)相關(guān)外文翻譯(節(jié)選)
- gps相關(guān)外文翻譯
- 審計(jì)相關(guān)外文翻譯
- 紡織相關(guān)外文翻譯
- 消防相關(guān)外文翻譯
- java相關(guān)外文翻譯
- php相關(guān)外文翻譯
- plc相關(guān)外文翻譯
- 數(shù)學(xué)相關(guān)外文翻譯
- 能源相關(guān)外文翻譯
- 教學(xué)相關(guān)外文翻譯
- asp相關(guān)外文翻譯
- 水資源相關(guān)外文翻譯----中國(guó)的節(jié)水灌溉
- cad相關(guān)外文翻譯
- asp相關(guān)外文翻譯
- asp相關(guān)外文翻譯2
- 企業(yè)轉(zhuǎn)型相關(guān)外文翻譯
評(píng)論
0/150
提交評(píng)論