C 值型別與引用型別 上

2021-05-01 16:00:10 字數 3728 閱讀 4614

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 定...