[你必须知道的.NET]第九回:品味类型---值类型与引用类型(中)-规则无边
?發(fā)布日期:2007.5.28 作者:Anytao
?2007 Anytao.com ,原創(chuàng)作品,轉(zhuǎn)貼請(qǐng)注明作者和出處。
?
接上回[第八回:品味類型---值類型與引用類型(上)-內(nèi)存有理]的探討,繼續(xù)我們關(guān)注值類型和引用類型的話題。
本文將介紹以下內(nèi)容:
- 類型的基本概念?
- 值類型深入
- 引用類型深入
- 值類型與引用類型的比較及應(yīng)用
??
1.?引言
上回[第八回:品味類型---值類型與引用類型(上)-內(nèi)存有理]的發(fā)布,受到大家的不少關(guān)注,我們從內(nèi)存的角度了解了值類型和引用類型的所以然,留下的任務(wù)當(dāng)然是如何應(yīng)用類型的不同特點(diǎn)在系統(tǒng)設(shè)計(jì)、性能優(yōu)化等方面發(fā)揮其作用。因此,本回是對(duì)上回有力的補(bǔ)充,同時(shí)應(yīng)朋友的希望,我們盡力從內(nèi)存調(diào)試的角度來著眼一些設(shè)計(jì)的分析,這樣就有助于對(duì)這一主題進(jìn)行透徹和全面的理解,當(dāng)然這也是下一回的重點(diǎn)。
從內(nèi)存角度來討論值類型和引用類型是有理有據(jù)的,??而從規(guī)則的角度來了解值類型和引用類型是無邊無際的。本文旨在從上文呼應(yīng)的角度,來把這個(gè)主題徹底的融會(huì)貫通,無邊無跡的應(yīng)用,還是來自反復(fù)無常的實(shí)踐,因此對(duì)應(yīng)用我只能說以一個(gè)角度來闡釋觀點(diǎn),但是肯定不可能力求全局。因此,我們從以下幾個(gè)角度來完成對(duì)值類型與引用類型應(yīng)用領(lǐng)域的討論。?
2. 通用規(guī)則與比較
通用有規(guī)則:
- string類型是個(gè)特殊的引用類型,它繼承自System.Object肯定是個(gè)引用類型,但是在應(yīng)用表現(xiàn)上又凸現(xiàn)出值類型的特點(diǎn),那么究竟是什么原因呢?例如有如下的一段執(zhí)行:
?
?簡單的說是由于string的immutable特性,因此每次對(duì)string的改變都會(huì)在托管堆中產(chǎn)生一個(gè)新的string變量,上述string作為參數(shù)傳遞時(shí),實(shí)際上執(zhí)行了s=s操作,在托管堆中會(huì)產(chǎn)生一個(gè)新的空間,并執(zhí)行數(shù)據(jù)拷貝,所以才有了類似于按值傳遞的結(jié)果。但是根據(jù)我們的內(nèi)存分析可知,string在本質(zhì)上還是一個(gè)引用類型,在參數(shù)傳遞時(shí)發(fā)生的還是按址傳遞,不過由于其特殊的恒定特性,在函數(shù)內(nèi)部新建了一個(gè)string對(duì)象并完成初始化,但是函數(shù)外部取不到這個(gè)變化的結(jié)果,因此對(duì)外表現(xiàn)的特性就類似于按值傳遞。至于string類型的特殊性解釋,我推薦Artech的大作《深入理解string和如何高效地使用string》。
另外,string類型重載了==操作符,在類型比較是比較的是實(shí)際的字符串,而不是引用地址,因此有以下的執(zhí)行結(jié)果:
????????????string?bString?=?"123";
????????????Console.WriteLine((aString?==?bString));?//顯示為true,等價(jià)于aString.Equals(bString);
????????????string?cString?=?bString;
????????????cString?=?"456";
????????????Console.WriteLine((bString?==?cString));?//顯示為false,等價(jià)于bString.Equals(cString);
- 通常可以使用Type.IsValueType來判斷一個(gè)變量的類型是否為值類型,典型的操作為:?
????{?}
????public?class?isValueType_Test
????{
????????public?static?void?Main()
????????{
????????????MyStructTester?aStruct?=?new?MyStructTester();
????????????Type?type?=?aStruct.GetType();
????????????if?(type.IsValueType)
????????????{
????????????????Console.WriteLine("{0}?belongs?to?value?type.",?aStruct.ToString());
????????????}
?
????????}
????}
- .NET中以操作符ref和out來標(biāo)識(shí)值類型按引用類型方式傳遞,其中區(qū)別是:ref在參數(shù)傳遞之前必須初始化;而out則在傳遞前不必初始化,且在傳遞時(shí)必須顯式賦值。
- 值類型與引用類型之間的轉(zhuǎn)換過程稱為裝箱與拆箱,這值得我們以專門的篇幅來討論,因此留待后文詳細(xì)討論這一主題。
- sizeof()運(yùn)算符用于獲取值類型的大小,但是不適用于引用類型。
- 值類型使用new操作符完成初始化,例如:MyStruct aTest = new MyStruct(); 而單純的定義沒有完成初始化動(dòng)作,此時(shí)對(duì)成員的引用將不能通過編譯,例如:?
Console.WriteLine(aTest.X);?
- 引用類型在性能上欠于值類型主要是因?yàn)橐韵聨讉€(gè)方面:引用類型變量要分配于托管堆上;內(nèi)存釋放則由GC完成,造成一定的CG堆壓力;同時(shí)必須完成對(duì)其附加成員的內(nèi)存分配過程;以及對(duì)象訪問問題。因此,.NET系統(tǒng)不能由純粹的引用類型來統(tǒng)治,性能和空間更加優(yōu)越和易于管理的值類型有其一席之地,這樣我們就不會(huì)因?yàn)橐粋€(gè)簡單的byte類型而進(jìn)行復(fù)雜的內(nèi)存分配和釋放工作。Richter就稱值類型為“輕量級(jí)”類型,簡直恰如其分,處理數(shù)據(jù)較小的情況時(shí),應(yīng)該優(yōu)先考慮值類型。
- 值類型都繼承自System.ValueType,而System.ValueType又繼承自System.Object,其主要區(qū)別是ValueType重寫了Equals方法,實(shí)現(xiàn)對(duì)值類型按照實(shí)例值比較而不是引用地址來比較,具體為:
char?b?=?'c';
Console.WriteLine((a.Equals(b)));?//會(huì)返回true;?
- 基元類型,是指編譯器直接支持的類型,其概念其實(shí)是針對(duì)具體編程語言而言的,例如C#或者VB.NET,通常對(duì)應(yīng)用.NET Framework定義的內(nèi)置值類型。這是概念上的界限,不可混淆。例如:int對(duì)應(yīng)于System.Int32,float對(duì)應(yīng)于System.Single。
比較出真知:
- 值類型繼承自ValueType(注意:而System.ValueType又繼承自System.Object);而引用類型繼承自System.Object。
- 值類型變量包含其實(shí)例數(shù)據(jù),每個(gè)變量保存了其本身的數(shù)據(jù)拷貝(副本),因此在默認(rèn)情況下,值類型的參數(shù)傳遞不會(huì)影響參數(shù)本身;而引用類型變量保存了其數(shù)據(jù)的引用地址,因此以引用方式進(jìn)行參數(shù)傳遞時(shí)會(huì)影響到參數(shù)本身,因?yàn)閮蓚€(gè)變量會(huì)引用了內(nèi)存中的同一塊地址。
- 值類型有兩種表示:裝箱與拆箱;引用類型只有裝箱一種形式。我會(huì)在下節(jié)以專門的篇幅來深入討論這個(gè)話題。
- 典型的值類型為:struct,enum以及大量的內(nèi)置值類型;而能稱為類的都可以說是引用類型。 struct和class主要的區(qū)別可以參見我的拙作《第四回:后來居上:class和struct》來詳細(xì)了解,也是對(duì)值類型和引用類型在應(yīng)用方面的有力補(bǔ)充。
- 值類型的內(nèi)存不由GC(垃圾回收,Gabage Collection)控制,作用域結(jié)束時(shí),值類型會(huì)自行釋放,減少了托管堆的壓力,因此具有性能上的優(yōu)勢(shì)。例如,通常struct比class更高效;而引用類型的內(nèi)存回收,由GC來完成,微軟甚至建議用戶最好不要自行釋放內(nèi)存。
- 值類型是密封的(sealed),因此值類型不能作為其他任何類型的基類,但是可以單繼承或者多繼承接口;而引用類型一般都有繼承性。?
- 值類型不具有多態(tài)性;而引用類型有多態(tài)性。
- 值類型變量不可為null值,值類型都會(huì)自行初始化為0值;而引用類型變量默認(rèn)情況下,創(chuàng)建為null值,表示沒有指向任何托管堆的引用地址。對(duì)值為null的引用類型的任何操作,都會(huì)拋出NullReferenceException異常。
- 值類型有兩種狀態(tài):裝箱和未裝箱,運(yùn)行庫提供了所有值類型的已裝箱形式;而引用類型通常只有一種形式:裝箱。
3. 對(duì)癥下藥-應(yīng)用場合與注意事項(xiàng)
現(xiàn)在,在內(nèi)存機(jī)制了解和通用規(guī)則熟悉的基礎(chǔ)上,我們就可以很好的總結(jié)出值類型和引用類型在系統(tǒng)設(shè)計(jì)時(shí),如何作出選擇?當(dāng)然我們的重點(diǎn)是告訴你,如何去選擇使用值類型,因?yàn)橐妙愋筒攀?NET的主體,不必花太多的關(guān)照就可以贏得市場。
3.1 值類型的應(yīng)用場合
- MSDN中建議以類型的大小作為選擇值類型或者引用類型的決定性因素。數(shù)據(jù)較小的場合,最好考慮以值類型來實(shí)現(xiàn)可以改善系統(tǒng)性能;
- 結(jié)構(gòu)簡單,不必多態(tài)的情況下,值類型是較好的選擇;
- 類型的性質(zhì)不表現(xiàn)出行為時(shí),不必以類來實(shí)現(xiàn),那么用以存儲(chǔ)數(shù)據(jù)為主要目的的情況下,值類型是優(yōu)先的選擇;
- 參數(shù)傳遞時(shí),值類型默認(rèn)情況下傳遞的是實(shí)例數(shù)據(jù),而不是內(nèi)存地址,因此數(shù)據(jù)傳遞情況下的選擇,取決于函數(shù)內(nèi)部的實(shí)現(xiàn)邏輯。值類型可以有高效的內(nèi)存支持,并且在不暴露內(nèi)部結(jié)構(gòu)的情況下返回實(shí)例數(shù)據(jù)的副本,從安全性上可以考慮值類型,但是過多的值傳遞也會(huì)損傷性能的優(yōu)化,應(yīng)適當(dāng)選擇;
- 值類型沒有繼承性,如果類型的選擇沒有子類繼承的必要,優(yōu)先考慮值類型;
- 在可能會(huì)引起裝箱與拆箱操作的集合或者隊(duì)列中,值類型不是很好的選擇,因?yàn)闀?huì)引起對(duì)值類型的裝箱操作,導(dǎo)致額外內(nèi)存的分配,例如在Hashtable。關(guān)于這點(diǎn)我將在后續(xù)的主題中重點(diǎn)討論。?
3.2 引用類型的應(yīng)用場合
- 可以簡單的說,引用類型是.NET世界的全值殺手,我們可以說.NET世界就是由類構(gòu)成的,類是面向?qū)ο蟮幕靖拍?#xff0c;也是程序框架的基本要素,因此靈活的數(shù)據(jù)封裝特性使得引用類型成為主流;
- 引用類型適用于結(jié)構(gòu)復(fù)雜,有繼承、有多態(tài),突出行為的場合;
- 參數(shù)傳遞情況也是考慮的必要因素;????
4. 再論類型判等
類型的比較通常有Equals()、ReferenceEquals()和==/!=三種常見的方法,其中核心的方法是Equals。我們知道Equals是System.Object提供的虛方法,用于比較兩個(gè)對(duì)象是否指向相同的引用地址,.NET Framework的很多類型都實(shí)現(xiàn)了對(duì)Equals方法的重寫,例如值類型的“始祖”System.ValueType就重載了Equal方法,以實(shí)現(xiàn)對(duì)實(shí)例數(shù)據(jù)的判等。因此,類型的判等也要從重寫或者重載Equals等不同的情況具體分析,對(duì)值類型和引用類型判等,這三個(gè)方法各有區(qū)別,應(yīng)多加注意。
4.1 值類型判等
- Equals,System.ValueType重載了System.Object的Equals方法,用于實(shí)現(xiàn)對(duì)實(shí)例數(shù)據(jù)的判等。
- ReferenceEquals,對(duì)值類型應(yīng)用ReferenceEquals將永遠(yuǎn)返回false。
- ==,未重載的==的值類型,將比較兩個(gè)值是否“按位”相等。
4.2 引用類型判等?
- Equals,主要有兩種方法,如下??
public?static?bool?Equals(object?objA,?object?objB);
?一種是虛方法,默認(rèn)為引用地址比較;而靜態(tài)方法,如果objA是與objB相同的實(shí)例,或者如果兩者均為空引用,或者如果objA.Equals(objB)返回true,則為true;否則為false。.NET的大部分類都重寫了Equals方法,因此判等的返回值要根據(jù)具體的重寫情況決定。?
- ReferenceEquals,靜態(tài)方法,只能用于引用類型,用于比較兩個(gè)實(shí)例對(duì)象是否指向同一引用地址。
- ==,默認(rèn)為引用地址比較,通常進(jìn)行實(shí)現(xiàn)了==的重載,未重載==的引用類型將比較兩個(gè)對(duì)象是否引用地址,等同于引用類型的Equals方法。因此,很多的.NET類實(shí)現(xiàn)了對(duì)==操作符的重載,例如System.String的==操作符就是比較兩個(gè)字符串是否相同。而==和equals方法的主要區(qū)別,在于多態(tài)表現(xiàn)上,==是被重載,而Equals是重寫。
有必要在自定義的類型中,實(shí)現(xiàn)對(duì)Equals和==的重寫或者重載,以提高性能和針對(duì)性分析。?
5. 再論類型轉(zhuǎn)換
類型轉(zhuǎn)換是引起系統(tǒng)異常一個(gè)重要的因素之一,因此在有必要在這個(gè)主題里做以簡單的總結(jié),我們不力求照顧全面,但是追去提綱挈領(lǐng)。常見的類型轉(zhuǎn)換包括:
- 隱式轉(zhuǎn)換:由低級(jí)類型項(xiàng)高級(jí)類型的轉(zhuǎn)換過程。主要包括:值類型的隱式轉(zhuǎn)換,主要是數(shù)值類型等基本類型的隱式轉(zhuǎn)換;引用類型的隱式轉(zhuǎn)換,主要是派生類向基類的轉(zhuǎn)換;值類型和引用類型的隱士轉(zhuǎn)換,主要指裝箱和拆箱轉(zhuǎn)換。
- 顯示轉(zhuǎn)換:也叫強(qiáng)制類型轉(zhuǎn)換。但是轉(zhuǎn)換過程不能保證數(shù)據(jù)的完整性,可能引起一定的精度損失或者引起不可知的異常發(fā)生。轉(zhuǎn)換的格式為,?
例如:int a = (int)(b + 2.02);
- 值類型與引用類型的裝箱與拆箱是.NET中最重要的類型轉(zhuǎn)換,不恰當(dāng)?shù)霓D(zhuǎn)換操作會(huì)引起性能的極大損耗,因此我們將以專門的主題來討論。
- 以is和as操作符進(jìn)行類型的安全轉(zhuǎn)換,詳見本人拙作《第一回:恩怨情仇:is和as》。
- System.Convert類定義了完成基本類型轉(zhuǎn)換的便捷實(shí)現(xiàn)。
- 除了string以外的其他類型都有Parse方法,用于將字符串類型轉(zhuǎn)換為對(duì)應(yīng)的基本類型;
- 使用explicit或者implicit進(jìn)行用戶自定義類型轉(zhuǎn)換,主要給用戶提高自定義的類型轉(zhuǎn)換實(shí)現(xiàn)方式,以實(shí)現(xiàn)更有目的的轉(zhuǎn)換操作,轉(zhuǎn)換格式為,
?例如:
public?Student{
????//
????
????static?public?explicite?opertator?Student(string?name,?int?age)
????{
????????return?new?Student(name,?age);
????}
????//
}
其中,所有的轉(zhuǎn)換都必須是static的。??
6. 結(jié)論
現(xiàn)在,我們從幾個(gè)角度延伸了上回對(duì)值類型和引用類型的分析,正如本文開頭所言,對(duì)類型的把握還有很多可以挖掘的要點(diǎn),但是以偏求全的辦法我認(rèn)為還是可取的,尤其是在技術(shù)探求的過程中,力求面面俱到的做法并不是好事。以上的幾個(gè)角度,我認(rèn)為是對(duì)值類型和引用類型把握的必經(jīng)之路,否則在實(shí)際的系統(tǒng)開發(fā)中常常會(huì)在細(xì)小的地方栽跟頭,摸不著頭腦。
品味類型,我們以應(yīng)用為要點(diǎn)撬開值類型和引用類型的規(guī)矩與方圓。
品味類型,我們將以示例為導(dǎo)航,開動(dòng)一個(gè)層面的深入分析,下回《第十回:品味類型---值類型與引用類型(下)-應(yīng)用征途》我們?cè)僖姟?/p>
??
參考文獻(xiàn)
(USA)Jeffrey Richter, Applied Microsoft .NET Framework Programming
(USA)David Chappell, Understanding .NET
??
溫故知新
[開篇有益]
[第一回:恩怨情仇:is和as]
[第二回:對(duì)抽象編程:接口和抽象類]
[第三回:歷史糾葛:特性和屬性]
[第四回:后來居上:class和struct]
[第五回:深入淺出關(guān)鍵字---把new說透]
[第六回:深入淺出關(guān)鍵字---base和this]
[第七回:品味類型---從通用類型系統(tǒng)開始]
[第八回:品味類型---值類型與引用類型(上)-內(nèi)存有理]
?2007 Anytao.com
原創(chuàng)作品,轉(zhuǎn)貼請(qǐng)注明作者和出處,留此信息。
本貼子以“現(xiàn)狀”提供且沒有任何擔(dān)保,同時(shí)也沒有授予任何權(quán)利。
This posting is provided "AS IS" with no warranties, and confers no rights.
總結(jié)
以上是生活随笔為你收集整理的[你必须知道的.NET]第九回:品味类型---值类型与引用类型(中)-规则无边的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 词典建立过程缓慢的解决~~子系统构架重新
- 下一篇: Windows Live Messeng