《CLR via C#》作者Jeffrey Richter的話來說片仿,“不理解【引用類型】和【值類型】區(qū)別的程序員將會給代碼引入詭異的bug和性能問題(I believe that a developer who misunderstands the difference between reference types and value types will introduce subtle bugs and performance issues into their code.)”彬坏。這就要求我們正確理解和使用值類型和引用類型嚼松。
C#中數(shù)據(jù)類型為CTS(Common Type System)岛啸,包括值類型和引用類型椅棺;值類型包含【布爾類型】攘残、【字符型】和【枚舉類型】讨惩。
枚舉類型(enum)是由一組特定常量構成的一組數(shù)據(jù)結構,是值類型的一種特殊形式氏身,它從System.Enum繼承而來巍棱,并為基礎類型的值提供替代名詞。枚舉類型有名稱蛋欣、基礎類型和一組字段航徙。基礎類型必須是內置的整數(shù)類型陷虎,字段是靜態(tài)文本字段到踏,其中的每一個字段都表示常數(shù),同一個值可以分配給多個字段尚猿,出現(xiàn)這種情況時窝稿,必須將其中某個值標記為主要枚舉值,以便進行反射和字符串轉換凿掂。枚舉聲明可以顯式地聲明 byte伴榔、sbyte纹蝴、short、ushort踪少、int塘安、uint、long 或 ulong 類型作為對應的基礎類型援奢,沒有顯式地聲明基礎類型的枚舉聲明意味著所對應的基礎類型是int兼犯。
用戶可以將基礎類型的值分配給枚舉,反之亦然(運行庫不要求強制轉換)集漾;也可創(chuàng)建枚舉的實例切黔,并調用System.Enum的方法以及對枚舉基礎類型定義的任何方法。對于枚舉還有以下附加限制:
1.枚舉不能定義自己的方法
2.枚舉不能實現(xiàn)接口
3.枚舉不能定義屬性或事件
注意點:
1.對于沒有賦值的枚舉類型具篇,聲明的第一個枚舉成員它的默值為0绕娘,以后的枚舉成員值是將前一個枚舉成員(按照文本順序)的值加1得到的。
2.允許多個枚舉成員有相同的值栽连。沒有顯示賦值的枚舉成員的值,總是前一個枚舉成員的值加1侨舆。
3.使用時注意類型轉換秒紧。
char代表無符號的16位整數(shù),數(shù)值范圍從0~65535挨下。 char類型的可能值對應于統(tǒng)一字符編碼標準(Unicode)的字符集熔恢。char類型與其他整數(shù)類型相比有以下兩點不同之處:
1.沒有其他類型到char類型的隱式轉換。即使是對于sbyte臭笆,byte和ushort這樣能完全使用char類型代表其值的類型叙淌, sbyte,byte和ushort到char的隱式轉換也不存在愁铺。
2.char類型的常量必須被寫為字符形式鹰霍,如果用整數(shù)形式,則必須帶有類型轉換前綴茵乱。
比如(char)10賦值形式有三種: char chsomechar="A"; char chsomechar="\x0065"; 十六進制 char chsomechar="\u0065 ;
unicode表示法字符型中有下列轉義符:
\'用來表示單引號
\"用來表示雙引號
\\ 用來表示反斜杠
\0 表示空字符
\a 用來表示感嘆號
\b 用來表示退格
\f 用來表示換頁
\n 用來表示換行
\r 用來表示回車
\t 用來表示水平tab
\v 用來表示垂直tab
float類型精確到小數(shù)點后面7位茂洒。double類型精確到小數(shù)點后面15位或16位株搔。decimal關鍵字表示128位數(shù)據(jù)類型车酣。同浮點型相比,decimal類型具有更高的精度和更小的范圍扳抽,這使它適合于財務和貨幣計算斤贰,精確到小數(shù)點后面28到29位智哀。
1. 通用類型系統(tǒng)
C#中,變量是值還是引用僅取決于其數(shù)據(jù)類型荧恍。C#的基本數(shù)據(jù)類型都以平臺無關的方式來定義瓷叫。C#的預定義類型并沒有內置于語言中,而是內置于.NET Framework中。.NET使用通用類型系統(tǒng)(CTS)定義了可以在中間語言(IL)中使用的預定義數(shù)據(jù)類型赞辩,所有面向.NET的語言都最終被編譯為 IL雌芽,即編譯為基于CTS類型的代碼。
例如辨嗽,在C#中聲明一個int變量時世落,聲明的實際上是CTS中System.Int32的一個實例。這具有重要的意義:
確保IL上的強制類型安全糟需;
實現(xiàn)了不同.NET語言的互操作性屉佳;
所有的數(shù)據(jù)類型都是對象。它們可以有方法洲押,屬性武花,等。例如:
int i;
i = 1;
string s;
s = i.ToString();
MSDN的這張圖說明了CTS中各個類型是如何相關的杈帐。注意体箕,類型的實例可以只是值類型或自描述類型,即使這些類型有子類別也是如此挑童。
2. 值類型
C#的所有值類型均隱式派生自System.ValueType: 結構體:struct(直接派生于System.ValueType)累铅;
數(shù)值類型:
整 型:sbyte(System.SByte的別名),short(System.Int16)站叼,int(System.Int32)娃兽,long (System.Int64),byte(System.Byte)尽楔,ushort(System.UInt16)投储,uint (System.UInt32),ulong(System.UInt64)阔馋,char(System.Char)玛荞;
浮點型:float(System.Single),double(System.Double)垦缅;
用于財務計算的高精度decimal型:decimal(System.Decimal)冲泥。
bool型:bool(System.Boolean的別名);
用戶定義的結構體(派生于System.ValueType)壁涎。
枚舉:enum(派生于System.Enum)凡恍;
可空類型(派生于System.Nullable泛型結構體,T?實際上是System.Nullable的別名)怔球。
每種值類型均有一個隱式的默認構造函數(shù)來初始化該類型的默認值嚼酝。例如:
int i = new int();
等價于:
Int32 i = new Int32();
等價于:
int i = 0;
等價于:
Int32 i = 0;
使用new運算符時,將調用特定類型的默認構造函數(shù)并對變量賦以默認值竟坛。在上例中闽巩,默認構造函數(shù)將值0賦給了i钧舌。MSDN上有完整的默認值表。
所有的值類型都是密封(seal)的涎跨,所以無法派生出新的值類型洼冻。
值得注意的是,System.ValueType直接派生于System.Object隅很。即System.ValueType本身是一個類類型撞牢,而不是值類型。其關鍵在于ValueType重寫了Equals()方法叔营,從而對值類型按照實例的值來比較屋彪,而不是引用地址來比較。
可以用Type.IsValueType屬性來判斷一個類型是否為值類型:
復制代碼 代碼如下:
TestType testType = new TestType ();
if (testType.GetType().IsValueType)
{
Console.WriteLine("{0} is value type.", testType.ToString());
}
3. 引用類型
C#有以下一些引用類型:
數(shù)組(派生于System.Array)
用戶定義的以下類型:
類:class(派生于System.Object)绒尊;
接口:interface(接口不是一個“東西”畜挥,所以不存在派生于何處的問題。Anders在《C# Programming Language》中說婴谱,接口只是表示一種約定[contract])蟹但;
委托:delegate(派生于System.Delegate);
object:(System.Object的別名)谭羔;
字符串:string(System.String的別名)矮湘。
可以看出:
引用類型與值類型相同的是,結構體也可以實現(xiàn)接口口糕;
引用類型可以派生出新的類型,而值類型不能磕蛇;
引用類型可以包含null值景描,值類型不能(可空類型功能允許將 null 賦給值類型);
引用類型變量的賦值只復制對對象的引用秀撇,而不復制對象本身超棺。而將一個值類型變量賦給另一個值類型變量時,將復制包含的值呵燕。
對于最后一條棠绘,經(jīng)常混淆的是string再扭。我曾經(jīng)在一本書的一個早期版本上看到String變量比string變量效率高氧苍;我還經(jīng)常聽說String是引用類型,string是值類型泛范,等等让虐。例如:
string s1 = "Hello, ";
string s2 = "world!";
string s3 = s1 + s2; ? //s3 is "Hello, world!"
這確實看起來像一個值類型的賦值。再如:
string s1 = "a";
string s2 = s1;
s1 = "b"; ? //s2 is still "a"
改變s1的值對s2沒有影響罢荡。這更使string看起來像值類型赡突。實際上对扶,這是運算符重載的結果,當s1被改變時惭缰,.NET在托管堆上為s1重新分配了內存浪南。這樣的目的,是為了將做為引用類型的string實現(xiàn)為通常語義下的字符串漱受。
4. 值類型和引用類型在內存中的部署
經(jīng)常聽說络凿,并且經(jīng)常在書上看到:值類型部署在棧上,引用類型部署在托管堆上拜效。實際上并沒有這么簡單喷众。
MSDN上說:托管堆上部署了所有引用類型。這很容易理解紧憾。當創(chuàng)建一個應用類型變量時:
object reference = new object();
關鍵字new將在托管堆上分配內存空間到千,并返回一個該內存空間的地址。左邊的reference位于棧上赴穗,是一個引用憔四,存儲著一個內存地址;而這個地址指向的內存(位于托管堆)里存儲著其內容(一個System.Object的實例)般眉。下面為了方便了赵,簡稱引用類型部署在托管推上。
再來看值類型甸赃∈裂矗《C#語言規(guī)范》 上的措辭是“結構體不要求在堆上分配內存(However, unlike classes, structs are value types and do not require heap allocation)”而不是“結構體在棧上分配內存”。這不免容易讓人感到困惑:值類型究竟部署在什么地方埠对?
5. 正確使用值類型和引用類型
這一部分主要參考《Effective C#》络断,希望能讓你加深對值類型和引用類型的理解。
C#中项玛,變量是值還是引用僅取決于其數(shù)據(jù)類型貌笨。
C#的值類型包括:結構體(數(shù)值類型,bool型襟沮,用戶定義的結構體)锥惋,枚舉,可空類型开伏。
C#的引用類型包括:數(shù)組膀跌,用戶定義的類、接口固灵、委托淹父,object,字符串怎虫。
數(shù)組的元素暑认,不管是引用類型還是值類型困介,都存儲在托管堆上。
引用類型在棧中存儲一個引用蘸际,其實際的存儲位置位于托管堆座哩。為了方便,本文簡稱引用類型部署在托管推上粮彤。
值類型總是分配在它聲明的地方:作為字段時根穷,跟隨其所屬的變量(實例)存儲;作為局部變量時导坟,存儲在棧上屿良。
值類型在內存管理方面具有更好的效率,并且不支持多態(tài)惫周,適合用作存儲數(shù)據(jù)的載體尘惧;引用類型支持多態(tài),適合用于定義應用程序的行為递递。
應該盡可能地將值類型實現(xiàn)為具有【常量性】和【原子性】的類型喷橙。
應該盡可能地確保0為值類型的有效狀態(tài)。
應該盡可能地減少裝箱和拆箱登舞。