來源:B.it
鏈接:http://www.cnblogs.com/ImBit/p/5484920.html
一、const和readonly
當(dāng)編譯期常量const被編譯成IL時,它就已經(jīng)被替換成所代表的字面數(shù)值。所以更改一個公有的編譯期常量的值,需要重新編譯所有引用到該常量的代碼以保證所有代碼使用的是最新的常量值。 相反,運(yùn)行時常量被編譯成IL時引用的是readonly的變量,而不是變量的值,只需要重新編譯更改了常量值的代碼,就能實(shí)現(xiàn)對其它已經(jīng)發(fā)布的代碼在二進(jìn)制層次上的兼容。
二、is、as
分兩種情況: 轉(zhuǎn)換的目標(biāo)類型是引用類型:使用is測試能否轉(zhuǎn)換成功,然后再用as進(jìn)行轉(zhuǎn)換(as在轉(zhuǎn)換對象為null時會返回null)。as和is不會執(zhí)行用戶自定義的轉(zhuǎn)換,只有當(dāng)運(yùn)行時類型是目標(biāo)類型或者是目標(biāo)的派生類型,才會轉(zhuǎn)換成功。執(zhí)行用戶自定義的轉(zhuǎn)換可以用強(qiáng)制轉(zhuǎn)換。
轉(zhuǎn)換的目標(biāo)類型是值類型:不能使用as,可以使用強(qiáng)制轉(zhuǎn)換。
三、條件編譯#if #endif和Conditional特性
兩者都適用于日常調(diào)試。 #if 可以穿插在函數(shù)中添加條件性代碼,但使用Conditional特性可以限制在函數(shù)層面上將條件性的代碼(如DEBUG)分離出來,保證代碼的良好結(jié)構(gòu)。
四、等同性判斷
(1)等同性在數(shù)學(xué)方面的幾個要點(diǎn): 自反(reflexive):表示任何對象都和其自身相等,無論a是什么類型,a==a都應(yīng)該返回true;
對稱(symmetric):意味著等同性判斷時的順序是無關(guān)緊要的,若a==b返回true,那么b==a也必然返回true;
可傳遞(transitive):含義是若a==b且b==c都返回true,那么a==c也必然返回true;
值相等(對應(yīng)于值類型):如果兩個值類型的變量類型相同,且包含同樣的內(nèi)容,則“值相等”;
引用相等(對應(yīng)于引用類型):如果兩個引用類型的變量指向的是同一個對象,則“引用相等”;
(2)C#中等同性判斷的四個方法 public static bool ReferenceEquals(object left,object right)
無論比較的是值類型還是引用類型,該方法判斷的依據(jù)都是對象標(biāo)識。所以用來比較兩個值類型,結(jié)果永遠(yuǎn)都是false,其原因在于裝箱。在創(chuàng)建自己的類時,幾乎不需要覆寫。
public static bool Equals(object left,object right)
當(dāng)不知道兩個變量的運(yùn)行時類型時,使用該方法進(jìn)行判斷。其內(nèi)部實(shí)現(xiàn)先引用ReferenceEquals進(jìn)行判斷,再拿left和right與null判斷,最后使用left.Equals(right)判斷。在創(chuàng)建自己的類時,幾乎不需要覆寫。
public virtual bool Equals(objcet rigth)
有兩種情況:
對于引用類型System.Object:使用對象標(biāo)識作為判斷,即比較兩個對象是否“引用相等”,默認(rèn)實(shí)現(xiàn)與ReferenceEquals完全一致。新建引用類型時無需覆寫。\
對于值類型System.ValueType:覆寫了System.Object中的該方法,實(shí)現(xiàn)了判斷是否”值相等“。因?yàn)闊o法得知當(dāng)前具體的值類型,所以使用了反射,效率較低。建議在新建值類型時覆寫該方法,覆寫的同時也要覆寫GetHashCode()方法。
public static bool operator==(MyClass left,MyClass right)
引用類型System.Object:使用對象標(biāo)識作為判斷。
值類型System.ValueType:覆寫了System.Object中的該方法,因?yàn)闊o法得知當(dāng)前具體的值類型,所以使用了反射,效率較低。建議在新建值類型時覆寫該方法,覆寫的同時也要覆寫GetHashCode()方法。
五、GetHashCode()陷阱
在引用類型(System.Object)中:GetHashCode()能正常工作,雖然它不必然會產(chǎn)生一個高效的分布。
在值類型(System.ValueType)中:只有在struct的第一個字段是只讀的情況下,GetHashCode()才能正常工作,只有當(dāng)?shù)谝粋€字段包含的值有著相對隨機(jī)的分布,GetHashCode()才會產(chǎn)生一個比較高效的散列碼。

GetHashCode覆寫規(guī)則
如果兩個對象相等(由operator==判斷),那么它們必須生成相同散列碼。否則,這樣的散列碼將無法用來查找容器中的對象。System.Object:滿足此規(guī)則;System.ValueType:并非所有參與等同性判斷的屬性都會用來進(jìn)行散列碼計(jì)算,而是返回struct類型中定義的第一個字段的散列碼作為當(dāng)前值類型的散列碼,所以可以認(rèn)定等同性判斷相等的散列碼肯定相等(反過來,散列碼相等同性判斷不一定相等)。滿足此規(guī)則。
2.對于任何一個對象A,A.GetHashCode()必須保持不變。不管在A上調(diào)用什么方法,A.GetHashCode()都必然總是返回同一個值。這樣可以確保放在“桶”中的對象總是位于正確的“桶”中。System.Object:滿足此規(guī)則;System.ValueType:struct中的第一個字段是一個常量字段,不會在生存期發(fā)生改變(稱為不可變的值類型),滿足此規(guī)則。
3.對于所有的輸入,散列函數(shù)應(yīng)該在所有整數(shù)中按照隨機(jī)分布生成散列碼。這樣,散列容器才能得到足夠的效率提升。System.Object:系統(tǒng)每創(chuàng)建一個對象時會指派一個唯一的對象鍵(一個整數(shù)值),從1開始,每創(chuàng)建一個任意類型的新對象,鍵值會隨之增長,雖然System.Object.GetHashCode()能正常工作,但因?yàn)椴皇请S機(jī)分布,效率不高,所以沒有滿足此規(guī)則,新建引用類型時建議覆寫GetHashCode();System.ValueType:依賴于第一個字段的使用方式,如果取任意值,GetHashCode()可以產(chǎn)生比較均勻的分布,但如果取相同的值則沒有此滿足規(guī)則。
六、委托
內(nèi)建的委托形式
Predicate:表示一個提供布爾型返回值的函數(shù);
Action:接受任意參數(shù)目的參數(shù);
Func:接受零到多個參數(shù),并返回單一結(jié)果;
.NET的委托都是多播委托(multicast delegate),多播委托將會把所有添加到該委托中的所有目標(biāo)函數(shù)組合成一個單一的調(diào)用。有兩點(diǎn)需要注意:
在多播委托調(diào)用過程中,每個目標(biāo)函數(shù)會被依次調(diào)用。委托對象本身不會捕獲異常。因此,任何目標(biāo)拋出的異常都會結(jié)束委托鏈的調(diào)用,如果有委托調(diào)用出現(xiàn)異常,那么這種方式不能保證安全;
整個調(diào)用的返回值將為最后一個函數(shù)調(diào)用的返回值,前面函數(shù)的返回值將會被忽略;
解決方法:自己遍歷調(diào)用列表,調(diào)用委托鏈上的每個目標(biāo)函數(shù)

委托應(yīng)用于回調(diào)
接受Predicate<>、Action<>、Func<>為參數(shù)的方法,如List.Find(Predicate p);有時傳入lambda表達(dá)式,編譯器會把lambda表達(dá)式轉(zhuǎn)換成方法,然后創(chuàng)建一個委托,指向該方法,然后調(diào)用委托實(shí)現(xiàn)回調(diào)。
委托應(yīng)用于事件
.NET的事件模式就是觀察者模式 public event EventHandler Log; 編譯器會自動創(chuàng)建類似下面的代碼,根據(jù)需要可以自己編寫
private EventHander log; public event EventHander Log { add{log=log+value;} remove{log=log-value;} }
事件相當(dāng)于一個委托集合,可以添加多個同類型委托(通過+=);
委托可以添加多個同類型方法(通過構(gòu)造函數(shù)或+=);
七、資源管理
托管堆上的內(nèi)存由GC(Garbage Collector 垃圾收集器,CLR中包含GC)進(jìn)行管理,其它資源由開發(fā)者負(fù)責(zé)。
.NET 提供兩種管理非托管資源生命周期的機(jī)制:終結(jié)器(finalizer,由GC調(diào)用,調(diào)用發(fā)生在對象成為垃圾之后的某個時間,時間不可預(yù)料)和IDisposable接口。
(1)GC
GC能夠判斷某個實(shí)體目前是否依舊被應(yīng)用程序的活動對象所引用,對于那些沒有被活動對象直接或間接引用的實(shí)體,GC會將其判斷為垃圾。
GC會在每次運(yùn)行時壓縮托管堆,壓縮托管堆能夠?qū)?dāng)前仍舊使用的對象放在連續(xù)的內(nèi)存中,因此空余空間也是一塊連續(xù)的內(nèi)存。

(2)終結(jié)器
終結(jié)器只是一種防御手段,僅僅能夠保證給定類型的對象所分配的非托管資源最終被釋放。GC會把需要執(zhí)行終結(jié)的對象放在專門的隊(duì)列中,然后讓另一個線程來執(zhí)行這些對象的終結(jié)器。這樣,GC可以繼續(xù)執(zhí)行其當(dāng)前的工作,在內(nèi)存中移除垃圾對象,而在下一次的GC調(diào)用中才會從內(nèi)存中移除這些已被終結(jié)的對象。
可以看到,需要調(diào)用終結(jié)器的對象將在內(nèi)存中多停留一個GC周期的時間(實(shí)際情況會比這個更復(fù)雜一點(diǎn),詳情請查看下面“代”的概念),所以應(yīng)該盡量少讓代碼的邏輯使用到終結(jié)器。

GC為了優(yōu)化執(zhí)行,引入了“代”(generation)的概念。可以快速地找到那些更有可能是垃圾的對象。自上一次垃圾收集以來,新創(chuàng)建的對象屬于第0代對象。若某個對象在經(jīng)歷過一次垃圾收集之后仍舊存活,那么將成為第1代對象。兩次及兩次以上垃圾收集后仍沒有被銷毀的對象就變成了第2代對象。
這樣能將局部變量和應(yīng)用程序生命周期一直使用的對象分開對待。第0代大多屬于局部變量。而成員變量和全局變量則會更快地成為第1代對象,直至第2代。GC將通過減少檢查第1代和第2代對象的次數(shù)來優(yōu)化執(zhí)行過程。在每個周期中,GC都會檢查第0代對象。一般來說,大概10個周期的GC中,會有一次去同時檢查第0代和第1代對象。
大概100個周期的GC中,會有一次同時檢查所有對象??梢钥吹揭粋€需要總結(jié)的對象可能會比普通對象多停留9個GC周期。而若是再次GC的時候仍沒有完成終結(jié)炒作,那么該對象將繼續(xù)被提升為第2代。對于第2代的對象,往往需要100次以上的GC周期才會有機(jī)會被清除。為了避免這個性能問題,建議使用IDisposable接口。
(3)Dispose()和Close()
使用了非系統(tǒng)資源的類型會自動在終結(jié)器中調(diào)用Dispose(),以便在使用者忘記的時候仍保證能正常釋放資源,但這些資源會在內(nèi)存中停留更長時間,所以最好的方案還是由使用者自己顯示地使用IDisposable接口的Dispose()來釋放。Dispose()并不是將對象從內(nèi)存中移除,而只是讓對象釋放掉其中的非托管資源。
Dispose()和Close()的區(qū)別(Dispose()比Close()要好一些)
Close:清理資源,對象已經(jīng)不需要被終結(jié),但一般沒有調(diào)用GC.SuppressFinalize(),所以對象仍舊在終結(jié)隊(duì)列中。
Dispose:清理資源,調(diào)用GC.SuppressFinalize()告知GC該對象不再需要被終結(jié)
使用IDisposable.Dispose()實(shí)現(xiàn)銷毀非托管資源的標(biāo)準(zhǔn)銷毀模式
釋放所有非托管資源;
釋放所有托管資源,包括釋放事件監(jiān)聽程序;
設(shè)定一個狀態(tài)標(biāo)識,表示該對象已經(jīng)被銷毀。若是在銷毀后再次對用對象的公有方法,那么應(yīng)該拋出ObjectDisposed異常;
調(diào)用GC.SuppressFinalize(this),跳過終結(jié)操作;
(4)using
using語句能以最簡單的方式保證用戶的對象可以正常銷毀,即使對象在調(diào)用操作時出現(xiàn)異常。當(dāng)有多個對象需要銷毀時,可以使用多個using塊或一個try/finally塊。
using(){}=try{}finally{xxx.Dispose();}
下面例子能保證當(dāng)obj不為null時正確清理到對象,當(dāng)obj為null時,using(null)也不會報錯,但不會做任何清理工作。
object obj=Factory.CreateInstance(); using(obj as IDisposable) { Console.Write(obj.ToString()); }
八、創(chuàng)建第一個實(shí)例所進(jìn)行的操作順序
創(chuàng)建某個類型的第一個實(shí)例時所進(jìn)行的操作順序,創(chuàng)建同樣類型的第二個以及以后的實(shí)例將從第5步開始執(zhí)行
靜態(tài)變量設(shè)置為0;
執(zhí)行靜態(tài)變量初始化器;
執(zhí)行基類的靜態(tài)構(gòu)造函數(shù);
執(zhí)行靜態(tài)構(gòu)造函數(shù);
實(shí)例變量設(shè)置為0;
執(zhí)行實(shí)例變量初始化器;
執(zhí)行基類中合適的實(shí)例構(gòu)造函數(shù);
執(zhí)行實(shí)例構(gòu)造函數(shù);
九、編譯的生命周期

|