[你必須知道的.NET] 第九回:品味類型---值類型與引用類型(中)-規則無邊
[2] [你必須知道的.NET] 第九回:品味類型---值類型與引用類型(中)-規則無邊
[3] [你必須知道的.NET] 第九回:品味類型---值類型與引用類型(中)-規則無邊
系列文章導航:
[你必須知道的.NET] 第四回:后來居上:class和struct
[你必須知道的.NET] 第五回:深入淺出關鍵字---把new說透
[你必須知道的.NET] 第六回:深入淺出關鍵字---base和this
[你必須知道的.NET] 第七回:品味類型---從通用類型系統開始
[你必須知道的.NET] 第八回:品味類型---值類型與引用類型(上)-內存有理
[你必須知道的.NET] 第九回:品味類型---值類型與引用類型(中)-規則無邊
[你必須知道的.NET] 第十回:品味類型---值類型與引用類型(下)-應用征途
[你必須知道的.NET] 第十一回:參數之惑---傳遞的藝術(上)
[你必須知道的.NET] 第十二回:參數之惑---傳遞的藝術(下)
[你必須知道的.NET] 第十三回:從Hello, world開始認識IL
[你必須知道的.NET] 第十四回:認識IL代碼---從開始到現在
[你必須知道的.NET] 第十六回:深入淺出關鍵字---using全接觸
[你必須知道的.NET]第二十二回:字符串駐留(上)---帶著問題思考
[你必須知道的.NET]第三十二回,深入.NET 4.0之,Tuple一二
接上回[第八回:品味類型---值類型與引用類型(上)-內存有理]的探討,繼續我們關注值類型和引用類型的話題。
本文將介紹以下內容:
• 類型的基本概念
• 值類型深入
• 引用類型深入
• 值類型與引用類型的比較及應用
1. 引言
上回[第八回:品味類型---值類型與引用類型(上)-內存有理]的發布,受到大家的不少關注,我們從內存的角度了解了值類型和引用類型的所以然,留下的任務當然是如何應用類型的不同特點在系統設計、性能優化等方面發揮其作用。因此,本回是對上回有力的補充,同時應朋友的希望,我們盡力從內存調試的角度來著眼一些設計的分析,這樣就有助于對這一主題進行透徹和全面的理解,當然這也是下一回的重點。
從內存角度來討論值類型和引用類型是有理有據的, 而從規則的角度來了解值類型和引用類型是無邊無際的。本文旨在從上文呼應的角度,來把這個主題徹底的融會貫通,無邊無跡的應用,還是來自反復無常的實踐,因此對應用我只能說以一個角度來闡釋觀點,但是肯定不可能力求全局。因此,我們從以下幾個角度來完成對值類型與引用類型應用領域的討論。
2. 通用規則與比較
通用有規則:
• string類型是個特殊的引用類型,它繼承自System.Object肯定是個引用類型,但是在應用表現上又凸現出值類型的特點,那么究竟是什么原因呢?例如有如下的一段執行:
string
簡單的說是由于string的immutable特性,因此每次對string的改變都會在托管堆中產生一個新的string變量,上述string作為參數傳遞時,實際上執行了s=s操作,在托管堆中會產生一個新的空間,并執行數據拷貝,所以才有了類似于按值傳遞的結果。但是根據我們的內存分析可知,string在本質上還是一個引用類型,在參數傳遞時發生的還是按址傳遞,不過由于其特殊的恒定特性,在函數內部新建了一個string對象并完成初始化,但是函數外部取不到這個變化的結果,因此對外表現的特性就類似于按值傳遞。至于string類型的特殊性解釋,我推薦Artech的大作《深入理解string和如何高效地使用string》。
另外,string類型重載了==操作符,在類型比較是比較的是實際的字符串,而不是引用地址,因此有以下的執行結果:
string aString = "123"; string bString = "123"; Console.WriteLine((aString == bString)); //顯示為true,等價于aString.Equals(bString); string cString = bString; cString = "456"; Console.WriteLine((bString == cString)); //顯示為false,等價于bString.Equals(cString);
• 通常可以使用Type.IsValueType來判斷一個變量的類型是否為值類型,典型的操作為:
Code
• .NET中以操作符ref和out來標識值類型按引用類型方式傳遞,其中區別是:ref在參數傳遞之前必須初始化;而out則在傳遞前不必初始化,且在傳遞時必須顯式賦值。
• 值類型與引用類型之間的轉換過程稱為裝箱與拆箱,這值得我們以專門的篇幅來討論,因此留待后文詳細討論這一主題。
• sizeof()運算符用于獲取值類型的大小,但是不適用于引用類型。
• 值類型使用new操作符完成初始化,例如:MyStruct aTest = new MyStruct(); 而單純的定義沒有完成初始化動作,此時對成員的引用將不能通過編譯,例如:
Code MyStruct aTest; Console.WriteLine(aTest.X);
• 引用類型在性能上欠于值類型主要是因為以下幾個方面:引用類型變量要分配于托管堆上;內存釋放則由GC完成,造成一定的CG堆壓力;同時必須完成對其附加成員的內存分配過程;以及對象訪問問題。因此,.NET系統不能由純粹的引用類型來統治,性能和空間更加優越和易于管理的值類型有其一席之地,這樣我們就不會因為一個簡單的byte類型而進行復雜的內存分配和釋放工作。Richter就稱值類型為“輕量級”類型,簡直恰如其分,處理數據較小的情況時,應該優先考慮值類型。
• 值類型都繼承自System.ValueType,而System.ValueType又繼承自System.Object,其主要區別是ValueType重寫了Equals方法,實現對值類型按照實例值比較而不是引用地址來比較,具體為:
Code char a = 'c'; char b = 'c'; Console.WriteLine((a.Equals(b))); //會返回true;
• 基元類型,是指編譯器直接支持的類型,其概念其實是針對具體編程語言而言的,例如C#或者VB.NET,通常對應用.NET Framework定義的內置值類型。這是概念上的界限,不可混淆。例如:int對應于System.Int32,float對應于System.Single。