abstract:CLR支持兩種類型:引用類型和值類型。雖然FCL的大多數(shù)類型都是引用類型,但程序員用的最多的還是引用類型,引用類型總是從托管堆分配,c#的new操作符返回對象內(nèi)存地址-即指向?qū)ο髷?shù)據(jù)的內(nèi)存地址。使用引用類型必須注意性能問題。首先要認(rèn)清楚以下4個(gè)方面:1、內(nèi)存必須從托管堆分配。2、堆上分配的每個(gè)對象都有一些額外的成員,這些成員必須初始化。3、對象中的其它字節(jié)(為字段而設(shè))總是設(shè)為零。4、從托管堆分配
CLR支持兩種類型:引用類型和值類型。雖然FCL的大多數(shù)類型都是引用類型,但程序員用的最多的還是引用類型,引用類型總是從托管堆分配,c#的new操作符返回對象內(nèi)存地址-即指向?qū)ο髷?shù)據(jù)的內(nèi)存地址。使用引用類型必須注意性能問題。首先要認(rèn)清楚以下4個(gè)方面:
1、內(nèi)存必須從托管堆分配。
2、堆上分配的每個(gè)對象都有一些額外的成員,這些成員必須初始化。
3、對象中的其它字節(jié)(為字段而設(shè))總是設(shè)為零。
4、從托管堆分配對象時(shí),可能強(qiáng)制執(zhí)行一次垃圾回收。
如果所有類型都是引用類型,應(yīng)用程序的性能將會顯著下降。設(shè)想每次使用Int32值時(shí)都進(jìn)行一次內(nèi)存分配,性能會受到多么大的影響,為了提升簡單和常用的類型的性能,CLR提供了名為‘值類型’的輕量級類型,值類型的實(shí)例一般在線程棧上分配,在代表值類型實(shí)例的變量中不包含指向?qū)嵗闹羔?。相反,變量中包含了?shí)例本身的字段。由于變量已經(jīng)包含了實(shí)例的字段。因此,值類型的使用緩解了托管堆的壓力,并減少了應(yīng)用程序生存期內(nèi)的垃圾回收次數(shù)。
文檔清楚指出哪些是值類型,哪些是引用類型。在文檔中查看類型時(shí),任何成為‘類’的類型都是引用類型。例如:System.Exception類,System.IO.FileStream類以及System.Random類都是引用類型。相反所有的值類型都成為結(jié)構(gòu)或枚舉。例如:System.Int32結(jié)構(gòu)、System.Boolean結(jié)構(gòu)、System.Decimal結(jié)構(gòu)、System.TimeSpan結(jié)構(gòu)、System.DayOfWeek枚舉等等。
進(jìn)一步研究文檔,會發(fā)現(xiàn)所有的結(jié)構(gòu)都是抽象類型System.ValueType的直接派生類。System.ValueType本身又直接從System.Object派生。根據(jù)定義,所有值類型都必須從System.ValueType派生。CLR和所有編程語言都給予枚舉特殊待遇。
雖然不能在定義值類型時(shí)為他選擇基類型,但如果愿意,值類型可以實(shí)現(xiàn)一個(gè)或多個(gè)接口。除此之外,所有值類型都隱式密封,目的是防止將值類型用作其他引用類型或這類型的基類。例如:無法將Boolean、Char、Int32、Uint64、Single、Double、Decimal等類型來定義任何新類型。
以下代碼演示了引用類型和值類型的區(qū)別:
//引用類型(因?yàn)槭莄lass) class SomeRef { public Int32 x; } //值類型(因?yàn)槭莝truct) struct SomeVal { public Int32 x; } static void ValueTypeDemo() { SomeRef r1=new SomeRef(); //往堆上分配 SomeVal v1 = new SomeVal(); //往棧上分配 r1.x = 5; //提領(lǐng)指針 v1.x = 5; //在棧上修改 Console.WriteLine(r1.x); //顯示為5 Console.WriteLine(v1.x); //同樣顯示為5 SomeRef r2 = r1; //只復(fù)用指針(引用) SomeVal v2 = v1; //在棧上分配并復(fù)制成員 r1.x = 8; //r1.x和r2.x都會修改成新值 v1.x=9; //v1.x會修改,v2.x不會修改 Console.WriteLine(r1.x); //顯示8 Console.WriteLine(r2.x); //顯示8 Console.WriteLine(v1.x); //顯示9 Console.WriteLine(v2.x); //顯示5 }
上述代碼中,SomeVal用struct聲明,而不是用更常用的class。在C#中,常用struct聲明的類型是值類型,用class聲明的類型是引用類型。可以看出引用類型和值類型的區(qū)別相當(dāng)大。再代碼中使用類型時(shí),必須注意是值類型還是引用類型,因?yàn)檫@會極大的影響在代碼中表達(dá)自己意圖的方式。
上述代碼中有這樣一行:
SomeVal v1 = new SomeVal(); //往棧上分配
因?yàn)檫@行代碼的寫法,似乎要在托管堆上分配一個(gè)SomeVal的實(shí)例。但c#編譯器知道SomeVal是值類型,所以會生成正確的IL代碼,在線程棧上分配一個(gè)SomeVal的實(shí)例。c#還會確保值類型中所有的字段都初始化為零。
SomeVal v1; //在棧上分配空間
這一行生成的IL代碼也會在線程棧上分配實(shí)例,并將字段初始化為零。唯一的區(qū)別在于,如果使用new操作符,C#會認(rèn)為實(shí)例已經(jīng)初始化,以下代碼更清楚的進(jìn)行了說明:
//這兩行代碼都能夠編譯通過,因?yàn)閏#認(rèn)為v1的字段已經(jīng)初始化為0 SomeVal v1 = new SomeVal(); Int32 a= v1.x; //這兩行代碼不能夠編譯通過,因?yàn)閏#不認(rèn)為v1的字段已經(jīng)初始化為0 SomeVal v1; Int32 a= v1.x; //error cs0170: 使用了可能未賦值的字段“x”;
設(shè)計(jì)自己的類型時(shí),要仔細(xì)考慮清楚是否應(yīng)該定義成值類型還是引用類型。值類型有時(shí)候能提供更好的性能。具體的說,除非滿足一下全部條件,否則不應(yīng)該聲明為值類型。
1、類型具有基元類型的行為。也就是說是十分簡單的類型,沒有成員會修改類型的任何實(shí)例字段。
2、類型不需要從其他任何類型繼承。
3、類型也不派生出其它任何類型。