標籤:blog http 使用 strong 資料 os
1. 主要內容
類型的基本概念
實值型別深入
參考型別深入
實值型別與參考型別的比較及應用
2. 基本概念
C#中,變數是值還是引用僅取決於其資料類型。
C#的基礎資料型別 (Elementary Data Type)都以平台無關的方式來定義,C#的預定義類型並沒有內建於語言中,而是內建於.NET Framework中。.NET使用一般型別系統(CTS)定義了可以在中繼語言(IL)中使用的預定義資料類型,所有面向.NET的語言都最終被編譯為 IL,即編譯為基於CTS類型的代碼,
通用類型的系統的功能:
- 建立一個支援跨語言整合、型別安全和高效能代碼執行的架構。
- 提供一個支援完整實現多種程式設計語言的物件導向的模型。
- 定義各語言必須遵守的規則,有助於確保用不同語言編寫的對象能夠互動作用。
例如,在C#中聲明一個int變數時,聲明的實際上是CTS中System.Int32的一個執行個體。這具有重要的意義:
- 確保IL上的強制型別安全;
- 實現了不同.NET語言的互通性;
- 所有的資料類型都是對象。它們可以有方法,屬性,等。例如:
int i;
i = 1;
string s;
s = i.ToString();
CLR 支援兩種類型:實值型別和參考型別,
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);
- 可空類型(派生於System.Nullable<T>泛型結構體,T?實際上是System.Nullable<T>的別名)。
實值型別(Value Type),實值型別執行個體通常分配線上程的堆棧(stack)上,並且不包含任何指向執行個體資料的指標,因為變數本身就包含了其執行個體資料
C#有以下一些參考型別:
- 數組(派生於System.Array)
- 使用者用定義的以下類型:
- 類:class(派生於System.Object);
- 介面:interface(介面不是一個“東西”,所以不存在派生於何處的問題。Anders在《C# Programming Language》中說,介面只是表示一種約定[contract]);
- 委託:delegate(派生於System.Delegate)。
- object(System.Object的別名);
- 字串:string(System.String的別名)。
可以看出:
- 參考型別與實值型別相同的是,結構體也可以實現介面;
- 參考型別可以派生出新的類型,而實值型別不能;
- 參考型別可以包含null值,實值型別不能(可空類型功能允許將 null 賦給實值型別);
- 參考型別變數的賦值只複製對對象的引用,而不複製對象本身。而將一個實值型別變數賦給另一個實值型別變數時,將複製包含的值
2.1記憶體深入
2.2.1 記憶體機制
資料在記憶體中分配位置取決與該變數的資料類型,可知實值型別分配線上程的堆棧上,參考型別則分配在託管堆上,由GC控制回收,以下代碼和圖示範了參考型別和實值型別的區別:
private static class ReferenceVsValue {
// Reference type (because of ‘class‘)
private class SomeRef { public Int32 x; }
// Value type (because of ‘struct‘)
private struct SomeVal { public Int32 x; }
public static void Go() {
SomeRef r1 = new SomeRef(); //在堆上分配
SomeVal v1 = new SomeVal(); // 在棧上分配
r1.x = 5; // 提領指標
v1.x = 5; // 在棧修改
Console.WriteLine(r1.x); // 顯示”5”
Console.WriteLine(v1.x); //同樣顯示”5”
// 左半部分反映了執行以上代碼之後的情形
SomeRef r2 = r1; //只複製引用(指標)
SomeVal v2 = v1; // 在棧上分配並且複製成員
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"
//右半部分反映了在執行所有代碼之後的情況
}
}
圖5-1 圖解代碼執行時的記憶體配置情況
SomeVal是用Struct來聲明的,而不是用常用的Class,在C#中用Struct聲明的是實值型別,每個變數或者程式都有自己的堆棧,不同的變數不能公用一個記憶體位址因此中SomeRef和SomeVal一定佔用了不同的堆棧,變數經過傳遞後,對v1變數改變時,顯然不會影響到v2的資料,可以看出,堆棧中的v1,v2包含其實際資料,而r1,r2則在堆棧中儲存了其執行個體資料的引用地址,實際的資料儲存在託管堆中,因此就有可能不同變數儲存了 同一地址的資料引用,當從一個參考型別變數傳遞到另外一個相同的參考型別變數時,傳遞的是引用地址而不是實際的資料,所以改變一個變數的值會影響到另外一個變數的值,實值型別與參考型別在記憶體中的分配是決定其應用不同的根本原因,由此可以容易的解釋為什麼傳遞參數的時候,按值傳遞不會改變形參的值,而按地址傳遞會改變形參的值。
記憶體配置的幾點:
實值型別變數做為局部變數時,該執行個體將被建立在堆棧上;而如果實值型別變數作為類型的成員變數時,它將作為類型執行個體資料的一部分,同該類型的其他欄位都儲存在託管堆上,將在接下來的嵌套結構部分來詳細說明問題。
參考型別變數資料儲存在託管堆上,但是根據執行個體的大小有所區別,如下:如果執行個體的大小小於85000Byte時,則該執行個體將建立在GC堆上;而當執行個體大小大於等於85000byte時,則該執行個體建立在LOH(Large Object Heap)堆上。
2.2.2巢狀型別
嵌套結構就是在實值型別中嵌套定義了參考型別,或者在參考型別變數中嵌套定義了實值型別
public class NestedValueinRef
{
//aInt做為參考型別的一部分將分配在託管堆上
private int aInt;
public NestedValueinRef
{
//aChar則分配在該段代碼的線程棧上
char achar = ‘a‘;
}
} 圖5-2 記憶體配置圖可以表示為:
參考型別嵌套在實值型別時,記憶體的分配情況為:該參考型別將作為實值型別的成員變數,堆棧上將儲存該成員的引用,而成員的實際資料還是儲存在託管堆中.
public struct NestedRefinValue
{
public MyClass myClass;
public NestedRefinValue
{
myClass.X = 1;
myClass.Y = 2;
}
}
圖5-3 記憶體配置圖可以表示為: