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