版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進(jìn)行舉報(bào)或認(rèn)領(lǐng)
文檔簡介
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è)簡單的事實(shí):.Net應(yīng)用程序是在一個(gè)托管的環(huán)境里運(yùn)行的,這個(gè)環(huán)境和不同的設(shè)計(jì)器有很大的沖突,這就才有了Effective C#。極大限度上的討論這個(gè)環(huán)境的好處,須要把你對本地化環(huán)境的想法改變?yōu)?Net CLR。也就意味著要明白.Net的垃圾回收器。在你明白這一章里所推薦的內(nèi)容時(shí),有必要對.Net的內(nèi)存管理環(huán)境有個(gè)大概的了解。那我們就開始大概的了解一下吧。</p><p> 垃圾回
4、收器(GC)為你控制托管內(nèi)存。不像本地運(yùn)行環(huán)境,你不用負(fù)責(zé)對內(nèi)存泄漏,不定指針,未初始化指針,或者一個(gè)其它內(nèi)存管理的服務(wù)問題。但垃圾回收器前不是一個(gè)神話:你一樣要自己清理。你要對非托管資源負(fù)責(zé),例如文件句柄,數(shù)據(jù)鏈接,GDI+對象,COM對象,以及其它一些系統(tǒng)對象。</p><p> 這有一個(gè)好消息:因?yàn)镚C管理內(nèi)存,明確的設(shè)計(jì)風(fēng)格可以更容易的實(shí)現(xiàn)。循環(huán)引用,不管是簡單關(guān)系還是復(fù)雜的網(wǎng)頁對象,都非常容易。GC的
5、標(biāo)記以及嚴(yán)謹(jǐn)?shù)母咝惴梢詸z測到這些關(guān)系,并且完全的刪除不可達(dá)的網(wǎng)頁對象。GC是通過對從應(yīng)用程序的根對象開始,通過樹形結(jié)構(gòu)的“漫游”來斷定一個(gè)對象是否可達(dá)的,而不是強(qiáng)迫每個(gè)對象都保持一些引用跟蹤,COM就是這樣的。DataSet就是一個(gè)很好的例子,展示了這樣的算法是如何簡化并決定對象的所屬關(guān)系的。DataSet是一個(gè)DataTable的集合,而每一個(gè)DataTable又是DataRow的集合,每一個(gè)DataRow又是DataItem的集
6、合,DataColum定義了這些類型的關(guān)系。這里就有一些從DataItem到它的列的引用。而同時(shí),DataTime也同樣有一個(gè)引用到它的容器上,也就是DataRow。DataRow包含引用到DataTable,最后每個(gè)對象都包含一個(gè)引用到DataSet。</p><p> 如果這還不夠復(fù)雜,那可以創(chuàng)建一個(gè)DataView,它提供對經(jīng)過過濾后的數(shù)據(jù)表的順序訪問。這些都是由DataViewManager管理的。所有
7、這些貫穿網(wǎng)頁的引用構(gòu)成了DataSet。釋放內(nèi)存是GC的責(zé)任。因?yàn)?Net框架的設(shè)計(jì)者讓你不必釋放這些對象,這些復(fù)雜的網(wǎng)頁對象引用不會造成問題。沒有必須關(guān)心這些網(wǎng)頁對象的合適的釋放順序,這是GC的工作。GC的設(shè)計(jì)結(jié)構(gòu)可以簡化這些問題,它可以識別這些網(wǎng)頁對象就是垃圾。在應(yīng)用程序結(jié)束了對DataSet的引用后,沒有人可以引用到它的子對象了(譯注:就是DataSet里的對象再也引用不到了)。因此,網(wǎng)頁里還有沒有對象循環(huán)引用DataSet,Da
8、taTables已經(jīng)一點(diǎn)也不重要了,因?yàn)檫@些對象在應(yīng)用程序都已經(jīng)不能被訪問到了,它們是垃圾了。</p><p> 垃圾回收器在它獨(dú)立的線程上運(yùn)行,用來從你的程序里移除不使用的內(nèi)存。而且在每次運(yùn)行時(shí),它還會壓縮托管堆。壓縮堆就是把托管堆中活動的對象移到一起,這樣就可以空出連續(xù)的內(nèi)存。圖2.1展示了兩個(gè)沒有進(jìn)行垃圾回收時(shí)的內(nèi)存快照。所有的空閑內(nèi)存會在垃圾回收進(jìn)行后連續(xù)起來。</p><p>
9、 圖2.1 垃圾回收器不僅僅是移動不使用的內(nèi)存,還移除動其它的對象,從而壓縮使用的內(nèi)存,讓出最多的空閑內(nèi)存。 </p><p> 正如你剛開始了解的,垃圾回收器的全部責(zé)任就是內(nèi)存管理。但,所有的系統(tǒng)資源都是你自己負(fù)責(zé)的。你可以通過給自己的類型定義一個(gè)析構(gòu)函數(shù),來保證釋放一些系統(tǒng)資源。析構(gòu)函數(shù)是在垃圾回收器把對象從內(nèi)存移除前,由系統(tǒng)調(diào)用的。你可以,也必須這樣來釋放任何你所占用的非托管資源。對象的析構(gòu)函數(shù)有時(shí)是在
10、對象成為垃圾之后調(diào)用的,但是在內(nèi)存歸還之前。這個(gè)非確定的析構(gòu)函數(shù)意味著在你無法控制對象析構(gòu)與停止使用之間的關(guān)系(譯注:對象的析構(gòu)與對象的無法引用是兩個(gè)完全不同的概念。關(guān)于GC,本人推薦讀者參考一下Jeffrey的".Net框架程序設(shè)計(jì)(修訂版)"中討論的垃圾回收器)。對C++來說這是個(gè)重大的改變,并且這在設(shè)計(jì)上有一個(gè)重大的分歧。有經(jīng)驗(yàn)的C++程序員寫的類總在構(gòu)造函數(shù)內(nèi)申請內(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)格,它保證資源無異常的釋放。但這在C#里不工作,至少,與這不同。明確的析構(gòu)函數(shù)不是.Net環(huán)境或者C#的一部份。強(qiáng)行用C++的風(fēng)格在C#里使用析構(gòu)函數(shù)不會讓它正常的工作。在C#里,析構(gòu)函數(shù)確實(shí)是正確的運(yùn)行了,但它不是即時(shí)運(yùn)行的。在前面那個(gè)例子里,代碼最終在critical section上,但在C#里,當(dāng)析構(gòu)函數(shù)存在時(shí),它并不是在critical section上。它會在后面的某個(gè)未知時(shí)間上運(yùn)
17、行。你不知道是什么時(shí)候,你也無法知道是什么時(shí)候。</p><p> 依懶于析構(gòu)函數(shù)同樣會導(dǎo)致性能上的損失。須要析構(gòu)的對象在垃圾回收器上放置了一劑性能毒藥。當(dāng)GC發(fā)現(xiàn)某個(gè)對象是垃圾但是須要析構(gòu)時(shí),它還不能直接從內(nèi)存上刪除這個(gè)對象。首先,它要調(diào)用析構(gòu)函數(shù),但析構(gòu)函數(shù)的調(diào)用不是在垃圾回收器的同一個(gè)線程上運(yùn)行的。取而代之的是,GC不得不把對象放置到析構(gòu)隊(duì)列中,讓另一個(gè)線程讓執(zhí)行所有的析構(gòu)函數(shù)。GC繼續(xù)它自己的工作,從內(nèi)
18、存上移除其它的垃圾。在下一個(gè)GC回收時(shí),那些被析構(gòu)了的對象才會再從內(nèi)存上移除。圖2.2展示了三個(gè)內(nèi)存使用不同的GC情況。注意,那些須要析構(gòu)的對象會待在內(nèi)存里,直到下一次GC回收。</p><p> 圖2.2 這個(gè)順序展示了析構(gòu)函數(shù)在垃圾回收器上起的作用。對象會在內(nèi)存里存在的時(shí)間更長,須要啟動另一個(gè)線程來運(yùn)行垃圾回收器。</p><p> 這用使你相信:那些須要析構(gòu)的對象在內(nèi)存至少多生存
19、一個(gè)GC回收循環(huán)。但,我是簡化了這些事。實(shí)際上,因?yàn)榱硪粋€(gè)GC的介入(譯注:其實(shí)只有一個(gè)GC,作者是想引用回收代的問題。),使得情況比這復(fù)雜得多。.Net回收器采用”代“來優(yōu)化這個(gè)問題。代可以幫助GC來很快的標(biāo)識那些看上去看是垃圾的對象。所以從上一次回后開始創(chuàng)建的對象稱為第0代對象,所有那些經(jīng)過一次GC回收后還存在的對象稱為第1代對象。所有那些經(jīng)過2次或者2次以上GC回收后還存在的對象稱為第2代對象。</p><p&
20、gt; 分代的目的就是用來區(qū)分臨時(shí)變量以及一些應(yīng)用程序的全局變量。第0代對象很可能是臨時(shí)的變量。成員變量,以及一些全局變量很快會成為第1代對象,最終成為第2代對象。</p><p> GC通過限制檢測第1以及第2代對象來優(yōu)化它的工作。每個(gè)GC循環(huán)都檢測第0代對象。粗略假設(shè)個(gè)GC會超過10次檢測來檢測第0代對象,而要超過100次來檢測所有對象。再次考慮析構(gòu)函數(shù)的開銷:一個(gè)須要析構(gòu)函數(shù)的對象可能要比一個(gè)不用析構(gòu)函
21、數(shù)的對象在內(nèi)存里多待上9個(gè)GC回收循環(huán)。如果它還沒有被析構(gòu),它將會移到第2代對象。在第2代對象中,一個(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ù)來確保清理非內(nèi)存資源。析構(gòu)函數(shù)會對你的應(yīng)用程序性能產(chǎn)生一些影響,但你必須使用它們來防止資源泄漏(譯注:請注意理
22、解非內(nèi)存資源是什么,一般是指文件句柄,網(wǎng)絡(luò)資源,或者其它不能在內(nèi)存中存放的資源)。通過實(shí)現(xiàn)IDisposable接口來避免析構(gòu)函數(shù)在垃圾回收器上造成的性能損失。接下來的具體的原則將會幫助你更有效的使用環(huán)境來開發(fā)程序。</p><p> Effective C# 原則12:選擇變量初始化而不是賦值語句</p><p> 一些類經(jīng)常不只一個(gè)構(gòu)造函數(shù)。時(shí)間一長,就難得讓它的成員變量以及構(gòu)造函
23、數(shù)進(jìn)行同步了。最好的確保這樣的事不會發(fā)生的方法就是:在聲明就是的時(shí)間就直接初始化,而不是在每個(gè)構(gòu)造函數(shù)內(nèi)進(jìn)行賦值。而且你應(yīng)該使用初始化器語法同時(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> 忽略你最終會給MyClass添加多少個(gè)構(gòu)造函數(shù),_coll會正確的初始化。編譯器會產(chǎn)生一些代碼,使得在你的任何一個(gè)構(gòu)造函數(shù)調(diào)用前,都會初始化你聲明的實(shí)例變量。當(dāng)你添加一個(gè)新的構(gòu)造函數(shù)時(shí),_coll就給你初始化了。當(dāng)你添加了一個(gè)新的變量,你不用在
25、所有的構(gòu)造函數(shù)里添加初始化代碼;直接在聲明的地方對它進(jìn)行初始化就行了。同樣重要的是:如果你沒有明確的聲明任何一個(gè)構(gòu)造函數(shù),編譯會默認(rèn)的給你添加一個(gè),并且把所有的變量初始化過程都添加到這個(gè)構(gòu)造函數(shù)里。</p><p> 初始化器更像是一個(gè)到構(gòu)造函數(shù)的方便的快捷方法。初始化生成的代碼會放置在類型的構(gòu)造函數(shù)之前。初始化會在執(zhí)行類型的基類的構(gòu)造函數(shù)之前被執(zhí)行,并且它們是按你聲明的先后關(guān)系順序執(zhí)行的。</p>
26、<p> 使用初始化器是一個(gè)最簡單的方法,在你的類型里來避免使用一些沒有賦值的變量,但這并不是很好。下面三種情況下,你不應(yīng)該使用初始化器語法。首先就是,如果你是初始化一個(gè)對象為0,或者為null。系統(tǒng)默認(rèn)會在你任何代碼執(zhí)行前,為所有的內(nèi)容都初始化為0。系統(tǒng)置0的初始化是基于底層的CPU指令,對整個(gè)內(nèi)存塊設(shè)置。你的任何其它置0的初始化語句是多余的。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> 兩條語句都是把變量置為0。第一個(gè)是通過設(shè)置包含_MyVal1的內(nèi)存來置0;而第二個(gè)是通過IL指令initobj,這對變量_My
28、Val2會產(chǎn)生裝箱與拆箱操作。這很要花一點(diǎn)額外的時(shí)間(參見原則17)。</p><p> 第二個(gè)低效率的是在你為一個(gè)對象添加兩個(gè)構(gòu)造函數(shù)時(shí)會產(chǎn)生。你使用初始化器初始化變量,而所有的構(gòu)造函數(shù)也對這些變量進(jìn)行了初始化。這個(gè)版本的MyClass兩個(gè)不同的ArrayList對象在它的構(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對象時(shí),特別指定集合的大小,你創(chuàng)建了兩個(gè)數(shù)組列表。其中一個(gè)很快成為垃圾對象。初始化器在所有的構(gòu)造函數(shù)之前會執(zhí)行,構(gòu)造函數(shù)會創(chuàng)建第2個(gè)數(shù)組列表。編譯器產(chǎn)生了這個(gè)的一個(gè)版本,當(dāng)然這是你決不會手動寫出來的。</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)生的異??赡苎苌綄ο蟮耐饷妗D銦o法試圖在你的類里來捕獲它。你
33、應(yīng)該把那些初始化代碼移到構(gòu)造函數(shù)里,這樣你就可以捕獲異常從而保證你的代碼很友好(參見原則45)。</p><p> 變量初始化器是一個(gè)最簡單的方法,在忽略構(gòu)造函數(shù)時(shí)來保證成員變量被正確的初始化。初始化器在所有的構(gòu)造函數(shù)之前被執(zhí)行。使用這樣的語法意味著當(dāng)你在為后來發(fā)布的版本中添加了構(gòu)造函數(shù)時(shí),不會忘記添加恰當(dāng)?shù)某跏蓟綐?gòu)造函數(shù)里。當(dāng)構(gòu)造函數(shù)與初始化生成同樣的成員對象時(shí),就使用初始化器。閱讀簡單而且易于維護(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ù)來實(shí)現(xiàn)這個(gè)目的。一個(gè)類的靜態(tài)構(gòu)造函數(shù)是一個(gè)與眾不同的,它在所有的方法,變量或者屬性訪問前被執(zhí)行。你可以用這個(gè)函數(shù)來初始化靜態(tài)成員變量,強(qiáng)制使用單件模式,或者實(shí)現(xiàn)其它任
35、何在類型的實(shí)例可用前應(yīng)該完成的工作。你不能用任何的實(shí)例構(gòu)造函數(shù),其它特殊的私有函數(shù), 或者任何其它習(xí)慣方法來初始化一個(gè)變量。</p><p> 和實(shí)例的預(yù)置方法一樣,你可以把靜態(tài)的預(yù)置方法做為靜態(tài)構(gòu)造函數(shù)可替代的選擇。如果須要簡單的分配一個(gè)靜態(tài)成員,就直接使用初始化語法。當(dāng)你有更復(fù)雜的邏輯來初始化靜態(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、法簡單的實(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自動調(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將會直接終止你的應(yīng)用程序。正因?yàn)楫惓?,靜態(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ù)為你的類提供了最清爽的方法來
43、初始化靜態(tài)成員。與其它語言不同,它們被添加到C#語言中,是初始化靜態(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. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
- 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 眾賞文庫僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負(fù)責(zé)。
- 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。
最新文檔
- effective_c#中文版改善c#程序的50種方法
- effectivec#改善c#程序的50種方法
- 計(jì)算機(jī)外文翻譯--c#設(shè)計(jì)模式
- 計(jì)算機(jī)外文翻譯--c#程序的回歸測試選擇
- 計(jì)算機(jī)外文翻譯--c#程序的回歸測試選擇
- 計(jì)算機(jī)外文翻譯--c#程序的回歸測試選擇
- 計(jì)算機(jī)外文翻譯---c#程序的回歸測試選擇
- 計(jì)算機(jī)外文翻譯---c#程序的回歸測試選擇
- 計(jì)算機(jī)外文翻譯--c#程序的回歸測試選擇(英文)
- 計(jì)算機(jī)外文翻譯--C#程序的回歸測試選擇.docx
- 計(jì)算機(jī)外文翻譯--C#程序的回歸測試選擇.docx
- 計(jì)算機(jī)外文翻譯--C#程序的回歸測試選擇.docx
- 計(jì)算機(jī)外文翻譯---asp.net入門c#版
- 計(jì)算機(jī)外文翻譯--C#程序的回歸測試選擇.docx
- 計(jì)算機(jī)外文翻譯--C#程序的回歸測試選擇(英文).pdf
- 計(jì)算機(jī)外文翻譯--C#程序的回歸測試選擇(英文).pdf
- 計(jì)算機(jī)外文翻譯--C#程序的回歸測試選擇(英文).pdf
- 計(jì)算機(jī)外文翻譯--C#程序的回歸測試選擇(英文).pdf
- 計(jì)算機(jī)c語言專業(yè)外文翻譯
- 計(jì)算機(jī)畢業(yè)設(shè)計(jì)外文翻譯--現(xiàn)代并發(fā)抽象c#
評論
0/150
提交評論