c#中,變數是值還是引用僅取決於其資料型別。
c#的基本資料型別都以平台無關的方式來定義。c#的預定義型別並沒有內置於語言中,而是內置於.net framework中。.net使用通用型別系統(cts)定義了可以在中間語言(il)中使用的預定義資料型別,所有面向.net的語言都最終被編譯為il,即編譯為基於cts型別的**。
例如,在c#中宣告乙個int變數時,宣告的實際上是cts中system.int32的乙個例項。這具有重要的意義:
inti;i =1
;string
s;s
=i.tostring();
msdn的這張圖說明了cts中各個型別是如何相關的。注意,型別的例項可以只是值型別或自描述型別,即使這些型別有子類別也是如此。
c#的所有值型別均隱式派生自system.valuetype:
每種值型別均有乙個隱式的預設建構函式來初始化該型別的預設值。例如:
inti
=new
int();
等價於:
int32 i
=new
int32();
等價於:
inti =0
; 等價於:
int32 i =0
; 使用new運算子時,將呼叫特定型別的預設建構函式並對變數賦以預設值。在上例中,預設建構函式將值0賦給了i。msdn上有完整的預設值表。
所有的值型別都是密封(seal)的,所以無法派生出新的值型別。
值得注意的是,system.valuetype直接派生於system.object。即system.valuetype本身是乙個類型別,而不是值型別。其關鍵在於valuetype重寫了equals()方法,從而對值型別按照例項的值來比較,而不是引用位址來比較。
可以用type.isvaluetype屬性來判斷乙個型別是否為值型別:
testtype testtype
=new
testtype ();
if(testtypetype.gettype().isvaluetype)
is value type.
", testtype.tostring());}
object(system.object的別名);
字串:string(system.string的別名)。
可以看出:
對於最後一條,經常混淆的是string。我曾經在一本書的乙個早期版本上看到string變數比string變數效率高;我還經常聽說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實現為通常語義下的字串。
經常聽說,並且經常在書上看到:值型別部署在棧上,引用型別部署在託管堆上。實際上並沒有這麼簡單。
msdn上說:託管堆上部署了所有引用型別。這很容易理解。當建立乙個應用型別變數時:
object
reference
=new
object
();
關鍵字new將在託管堆上分配記憶體空間,並返回乙個該記憶體空間的位址。左邊的reference位於棧上,是乙個引用,儲存著乙個記憶體位址;而這個位址指向的記憶體(位於託管堆)裡儲存著其內容(乙個system.object的例項)。下面為了方便,簡稱引用型別部署在託管推上。
再來看值型別。《c#語言規範》上的措辭是「結構體不要求在堆上分配記憶體(however, unlike classes, structs are value types and do not require heap allocation)」而不是「結構體在棧上分配記憶體」。這不免容易讓人感到困惑:值型別究竟部署在什麼地方?
考慮陣列:
int reference
=new
int[
100];
根據定義,陣列都是引用型別,所以int陣列當然是引用型別(即reference.gettype().isvaluetype為false)。
而int陣列的元素都是int,根據定義,int是值型別(即reference[i].gettype().isvaluetype為true)。那麼引用型別陣列中的值型別元素究竟位於棧還是堆?
如果用windbg去看reference[i]在記憶體中的具體位置,就會發現它們並不在棧上,而是在託管堆上。
實際上,對於陣列:
testtype testtypes
=new
testtype[
100];
如果testtype是值型別,則會一次在託管堆上為100個值型別的元素分配儲存空間,並自動初始化這100個元素,將這100個元素儲存到這塊記憶體裡。
如果testtype是引用型別,則會先在託管堆為testtypes分配一次空間,並且這時不會自動初始化任何元素(即testtypes[i]均為null)。等到以後有**初始化某個元素的時候,這個引用型別元素的儲存空間才會被分配在託管堆上。
更容易讓人困惑的是引用型別包含值型別,以及值型別包含引用型別的情況:
單看valuetypestructinstance,這是乙個結構體例項,感覺似乎是整塊扔到棧上的。但是欄位_referencetypefield是引用型別,區域性變數referencetypelocalvarible也是引用型別。
referencetypeclassinstance也有同樣的問題,referencetypeclassinstance本身是引用型別,似乎應該整塊部署在託管堆上。但字段_valuetypefield是值型別,區域性變數valuetypelocalvariable也是值型別,它們究竟是在棧上還是在託管堆上?
規律是:
我們來分析一下上面的**。對於引用型別例項,即referencetypeclassinstance:
而對於值型別例項,即valuetypestruct:
所以,簡單地說「值型別儲存在棧上,引用型別儲存在託管堆上」是不對的。必須具體情況具體分析。
這一部分主要參考《effective c#》,並非本人原創,希望能讓你加深對值型別和引用型別的理解。
c#中,我們用struct/class來宣告乙個型別為值型別/引用型別。
考慮下面的例子:
testtype testtypes
=new
testtype[
100];
如果testtye是值型別,則只需要一次分配,大小為testtye的100倍。而如果testtye是引用型別,剛開始需要100次分配,分配後陣列的各元素值為null,然後再初始化100個元素,結果總共需要進行101次分配。這將消耗更多的時間,造成更多的記憶體碎片。所以,如果型別的職責主要是儲存資料,值型別比較合適。
一般來說,值型別(不支援多型)適合儲存供 c#應用程式操作的資料,而引用型別(支援多型)應該用於定義應用程式的行為。
通常我們建立的引用型別總是多於值型別。如果以下問題的回答都為yes,那麼我們就應該建立為值型別:
C 值型別與引用型別 上
原帖 1.主要內容 型別的基本概念 值型別深入 引用型別深入 值型別與引用型別的比較及應用 2.基本概念 c 中,變數是值還是引用僅取決於其資料型別。c 的基本資料型別都以平台無關的方式來定義,c 的預定義型別並沒有內置於語言中,而是內置於.net framework中。net使用通用型別系統 ct...
C 值型別與引用型別 上
1.主要內容 型別的基本概念 值型別深入 引用型別深入 值型別與引用型別的比較及應用 2.基本概念 c 中,變數是值還是引用僅取決於其資料型別。c 的基本資料型別都以平台無關的方式來定義,c 的預定義型別並沒有內置於語言中,而是內置於.net framework中。net使用通用型別系統 cts 定...
C 值型別與引用型別 上
1.主要內容 型別的基本概念 值型別深入 引用型別深入 值型別與引用型別的比較及應用 2.基本概念 c 中,變數是值還是引用僅取決於其資料型別。c 的基本資料型別都以平台無關的方式來定義,c 的預定義型別並沒有內置於語言中,而是內置於.net framework中。net使用通用型別系統 cts 定...