clr支援兩種型別:值型別和引用型別。用jeffrey richter(《clr via c#》作者)的話來說,「不理解引用型別和值型別區別的程式設計師將會把**引入詭異的陷阱和諸多效能問題」。這就要求我們正確理解和使用值型別和引用型別。
一、概念
1. 值型別:資料儲存在記憶體的堆疊中,從堆疊中可以快速地訪問這些資料,因此,值型別表示實際的資料。
2. 引用型別:表示指向儲存在記憶體堆中的資料的指標或引用(包括類、介面、陣列和字串)。
二、區別:
基本區別在於它們在記憶體中的儲存方式。值型別只將值存放在記憶體中,這些值型別都儲存在堆疊中。原始資料型別(如bool和int)都屬於此型別。而引用型別的記憶體單元中只存放記憶體堆中物件的位址,而物件本身放在記憶體堆中。如果引用的值型別的值是null,則表示未引用任何物件。
值型別包括c#的基本型別(用關鍵字int、char、float等來宣告),結構(用struct關鍵字宣告的型別),列舉(用enum關鍵字宣告的型別);而引用型別包括類(用class關鍵字宣告的型別)和委託(用delegate關鍵字宣告的特殊類)。
1. 在c#中,變數是值還是引用僅取決於其基本資料型別。
c#的基本資料型別都與平台無關。c#的預定義型別並沒有內置於語言中,而是內置於.net framework中。.net使用通用型別系統(cts)定義可以在中間語言(il)中使用的預定義資料型別。c#中所有的資料型別都是物件。它們可以有方法、屬性等。例如,在c#中宣告乙個int變數時,宣告實際上是cts(通用型別系統)中system.int32的乙個例項:
int i;
i = 1;
string s;
s = i.tostring();
2. system.object和system.valuetype。
引用型別和值型別都繼承自system.object類。不同的是,幾乎所有的引用型別都直接從system.object繼承,而值型別則繼承其子類,即直接繼承system.valuetype。作為所有型別的基類,system.object提供了一組方法,這些方法在所有型別中都能找到。其中包含tostring方法及clone等方法。system.valuetype繼承system.object。它沒有新增任何成員,但覆蓋了所繼承的一些方法,使其更適合於值型別。
3. 值型別。
c#的所有值型別均隱式派生自system.valuetype:
結構體:struct(直接派生於system.valuetype)。
數值型別:整型,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)。
可空型別。
每種值型別均有乙個隱式的預設建構函式來初始化該型別的預設值。例如:
int i = 0;
等價於:
int i = new int();
使用new運算子時,將呼叫特定型別的預設建構函式並對變數賦予預設值。在上例中,預設建構函式將值0賦給了i。
所有的值型別都是密封(seal)的,所以無法派生出新的值型別。
值得注意的是,system.valuetype直接派生於system.object。即system.valuetype本身是乙個類型別,而不是值型別。其關鍵在於valuetype重寫了equals()方法,從而對值型別按照例項的值來比較,而不是引用位址來比較。可以用type.isvaluetype屬性來判斷乙個型別是否為值型別:
testtype testtype = new testtype ();
if (testtypetype.gettype().isvaluetype)
is value type.", testtype.tostring());
}
4. 引用型別
陣列(派生於system.array)
使用者需定義以下型別。
類:class(派生於system.object);
介面:inte***ce(介面不是乙個「東西」,所以不存在派生於何處的問題。介面只是表示一種contract約定[contract])。
委託:delegate(派生於system.delegate)。
object(system.object的別名);
字串:string(system.string的別名)。
可以看出:
引用型別與值型別相同的是,結構體也可以實現介面;引用型別可以派生出新的型別,而值型別不能;引用型別可以包含null值,值型別不能;引用型別變數的賦值只複製物件的引用,而不複製物件本身。而將乙個值型別變數賦給另乙個值型別變數時,將複製包含的值。
5. 記憶體分配。
值型別的例項經常會儲存在棧上的。但是也有特殊情況。如果某個類的例項有個值型別的字段,那麼實際上該字段會和類例項儲存在同乙個地方,即堆中。不過引用型別的物件總是儲存在堆中。如果乙個結構的字段是引用型別,那麼只有引用本身是和結構例項儲存在一起的(在棧或堆上,視情況而定)。如下例所示:
public struct valuetypestruct
}valuetypestruct valuetypestructinstance = new valuetypestruct();
valuetypestructinstance.method();
//referencetypeobject 和 referencetypelocalvariable 都在哪存放?
單看valuetypestructinstance,這是乙個結構體例項,感覺似乎是整塊都在棧上。但是欄位referencetypeobject是引用型別,區域性變數referencetypelocalvarible也是引用型別。
public class referencetypeclass
public void method()
}referencetypeclass referencetypeclassinstance = new referencetypeclass();
// _valuetypefield在哪存放?
referencetypeclassinstance.method();
// valuetypelocalvariable在哪存放?
referencetypeclassinstance也有同樣的問題,referencetypeclassinstance本身是引用型別,似乎應該整塊部署在託管堆上。但字段_valuetypefield是值型別,區域性變數valuetypelocalvariable也是值型別,它們究竟是在棧上還是在託管堆上?
對上面的情況正確的分析是:引用型別在棧中儲存乙個引用,其實際的儲存位置位於託管堆。為了方便,簡稱引用型別部署在託管堆上。值型別總是分配在它宣告的地方,作為欄位時,跟隨其所屬的變數(例項)儲存;作為區域性變數時,儲存在棧上。
6. 辨明值型別和引用型別的使用場合。
在c#中,我們用struct/class來宣告乙個型別為值型別/引用型別。考慮下面的例子:
sometype onetypes = new sometype[100];
如果sometype是值型別,則只需要一次分配,大小為sometype的100倍。而如果sometype是引用型別,剛開始需要100次分配,分配後陣列的各元素值為null,然後再初始化100個元素,結果總共需要進行101次分配。這將消耗更多的時間,造成更多的記憶體碎片。所以,如果型別的職責主要是儲存資料,值型別比較合適。
一般來說,值型別(不支援多型)適合儲存供 c#應用程式操作的資料,而引用型別(支援多型)應該用於定義應用程式的行為。通常我們建立的引用型別總是多於值型別。如果滿足下面情況,那麼我們就應該建立為值型別:
該型別的主要職責用於資料儲存。
該型別的共有介面完全由一些資料成員訪問屬性定義。
該型別永遠不可能有子類。
該型別不具有多型行為。
答案:在c#中,變數是值還是引用僅取決於其資料型別。
c#的值型別包括:結構體(數值型別、bool型、使用者定義的結構體),列舉,可空型別。
c#的引用型別包括:陣列,使用者定義的類、介面、委託,object,字串。陣列的元素,不管是引用型別還是值型別,都儲存在託管堆上。
引用型別在棧中儲存乙個引用,其實際的儲存位置位於託管堆。簡稱引用型別部署在託管推上。值型別總是分配在它宣告的地方:作為欄位時,跟隨其所屬的變數(例項)儲存;作為區域性變數時,儲存在棧上。值型別在記憶體管理方面具有更好的效率,並且不支援多型,適合用做儲存資料的載體;引用型別支援多型,適合用於定義應用程式的行為。
引用型別和值型別
c 是一種型別安全的語言。每乙個變數都要求定義為乙個特定的型別,並且要求儲存在變數中的值只能是這種型別的值。變數既能儲存值型別,也可以儲存引用型別,還可以是指標。這一課將講述前兩種型別,關於指標的討論我們將在下一課中進行。下面是關於值型別和引用型別不同點的概論 如果乙個變數v儲存的是值型別,則它直接...
引用型別和值型別
c 中值型別和引用型別作為方法引數傳遞的時候其實都可以說是 值 的傳遞,只不過這裡的 值 指代的東西有所區別。當方法的引數為值型別時,方法傳遞的是值本身的值。當方法的引數為引用型別時,方法傳遞的則是應用型別的引用的位址,也就是引用型別位址在棧上的值。舉個引用型別作為引數傳遞的例子 static vo...
值型別和引用型別
為了更好地說明兩種型別之間的區別,借用如下的 來說明 值型別引用型別 記憶體分配地點 分配在棧中 分配在堆中 效率效率高,不需要位址轉換 效率低,需要進行位址轉換 記憶體 使用完後,立即 使用完後,不是立即 等待gc 賦值操作 進行複製,建立乙個同值新物件 只是對原有物件的引用 函式引數與返回值 是...