1.值型別 Value Type
- 儲存位置:值型別的實例通常儲存在堆疊 (Stack) 上。如果值型別是某個物件的成員,則它會隨物件一起儲存在堆積 (Heap) 上。
- 複製行為:在對值型別進行賦值或傳遞參數時,是直接複製該值。代表對複製後的值進行修改,不會影響原始值。
- 例:
int
,bool
,float
,char
, 結構 (struct) 等。
2.參考型別 Reference Type
- 儲存位置:參考型別的實例通常儲存在堆積 (Heap) 上,而變數本身儲存在堆疊 (Stack) 上,變數持有的是對堆積中物件的引用。
- 複製行為:在對參考型別進行賦值或傳遞參數時,是複製引用,這意味著多個變數可以引用同一個物件。對其中一個引用的物件進行修改,其他引用也會看到這些改變。
- 例:
string
,array
,class
,interface
,delegate
等。
堆疊與堆積的區別:
堆疊 (Stack) 是一種後進先出 (LIFO) 的記憶體結構,通常用於儲存方法呼叫的本地變數和方法的呼叫記錄。堆積 (Heap) 是用來儲存物件和資料結構的動態記憶體空間,適合用於需要動態分配和釋放記憶體的情況。
3.動態型別 Dynamic Type
dynamic
關鍵字允許在編譯時期省略變數的型別檢查,而將型別檢查放在執行時期。這意味著編譯器不會對 dynamic
型別的變數進行靜態型別檢查,所有的型別檢查和解析都是在運行時期進行的。
以下是其特性:
由 CLR 決定:當使用
dynamic
時,.NET 的 Common Language Runtime (CLR) 在運行時期會決定變數的實際型別,並執行適當的操作。這使得dynamic
非常靈活,但同時也帶來了一些風險,例如在運行時期發生型別錯誤。靈活性與風險:使用
dynamic
提供了很大的靈活性,特別是在處理來自不同來源的資料或進行反射操作時。但也帶來了運行時期的型別安全風險和潛在的性能疑慮,因為所有的型別解析和操作都是在運行時期完成的。適用場合:
dynamic
通常用於需要與動態語言相互操作、處理 JSON 或 XML 等結構化但動態的資料、或使用反射和 COM 互操作的情況。
4.匿名型別 Anonymous Type
匿名型別主要用於臨時性、不需要多次重用的情況,特別是在 LINQ 查詢中。
var person = new { Name = "John", Age = 30 };
在這個例子中,person
是一個匿名型別,具有 Name
和 Age
屬性。編譯器會自動推斷這些屬性的類型,分別為 string
和 int
。
常見的使用場景:
LINQ 查詢
var people = new[] { new { FirstName = "John", LastName = "Doe", Age = 30 }, new { FirstName = "Jane", LastName = "Smith", Age = 25 }, new { FirstName = "Sam", LastName = "Brown", Age = 35 } }; var selectedPeople = people.Select(p => new { p.FirstName, p.LastName }); foreach (var person in selectedPeople) { Console.WriteLine($"FirstName: {person.FirstName}, LastName: {person.LastName}"); }
臨時資料結構
var product = new { Name = "Laptop", Price = 999.99, InStock = true }; Console.WriteLine($"Product: {product.Name}, Price: {product.Price}, In Stock: {product.InStock}");
4.1 匿名型別的限制
- 唯讀屬性:匿名型別的屬性是唯讀的,一旦賦值就不能改變。
- 作用範圍:匿名型別主要用於局部範圍內,無法在方法之外(如class的層級)重複使用。
- 無法顯式定義類型:因為匿名型別沒有顯式的類型名稱,所以無法在方法參數或回傳值中使用它們。
4.2 匿名型別與動態型別的區別
雖然匿名型別和動態型別(如 dynamic
)都可以在運行時決定結構,但它們有本質上的差異:
- 匿名型別:編譯時確定類型,提供編譯時類型檢查,屬性是靜態類型的。
- 動態型別:在運行時決定類型,編譯時不進行類型檢查,所有操作都是動態解析。
5. Implicit Type 隱式型別
隱式型別轉換允許將一種型別的物件用於原本預期另一種不同的型別,例如當使用 var
宣告變數時,編譯器會根據右側的初始化表達式來推斷變數的實際型別。
5.1 推斷型別
var number = 5; // 編譯器推斷 number 為 int (Value type)
var text = "hello"; // 編譬器推斷 text 為 string (Reference Type)
5.2. 型別推斷規則:
var
本身不是一種型別,而是編譯器用來自動推斷變數型別的關鍵字。- 編譯器會根據初始化表達式來決定變數的型別,這個過程在編譯時期完成。
5.3. 值型別和參考型別的行為:
- 當使用
var
宣告變數時,變數的實際型別決定了它是值型別還是參考型別。 - 這些變數在執行時的行為與使用顯式型別宣告的變數完全相同。
6. Nullable Type 可null型別
允許變數值為 null 的型別。按邏輯推論這不是用在參考型別(reference types),因為參考型別的變數值本來就可以是 null。即Nullable 的用途就是讓實質型別的變數可以是「空值」。
6.1 宣告與初始化
除了直接使用 System.Nullable 來宣告可為 null 的變數,C# 提供簡便的寫法:直接在實質型別後面加一個問號。
Nullable<int> x; // 打好多字
int? y; // 作用同上
6.2 賦值與取值
設定 Nullable 變數的值,可以透過 Value 屬性,亦可直接賦值
int? A;
A.value=3; //透過Value 屬性
int? B =30; //直接賦值
要注意的是:
- 使用
Value
屬性取得值或使用強制轉型 ((int)
) 取得值,但在變數為null
時會發生InvalidOperationException
。 - 可以使用空合併運算子 (
??
) 提供預設值。 - 除了
HasValue
屬性可以判斷變數是否有值,也可以使用可以用 == 或 != 運算子。C# 提供??
運算子,也就是二個連續的?符號。如下所示:
int? x = null;
int? y = x ?? 5; // 如果x為null, 則y=5
int? y = (x != null) ? x : 5; //與上一行是一樣的意思
補充:可null與非null型別混用
非null型別的變數可直接指定給null型別的變數,如:
int x=300;
int? y=x; // 隱含轉換
null型別的變數則未必可以指定給非null型別的變數,如:
int? y=3;
int x=y;
上述做法會在編譯時出現錯誤,如果改成
int x=(int) y; //明確轉型
可以編譯成功,但執行時還是會有錯誤,因為 x 是一般的實值型別,無法接受 null。
最後還有Generics Type(泛型),因為想深入研究故會再開一篇專門用於探討。