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