C# 是強型別語言。 每個變數和常數都有類型,評估為值的每個表達式也一樣。 C# 主要使用 規範型別系統。
規範型態系統使用名稱來識別每種型別。 在 C# 中,struct、classinterface以及類型(包括record類型)都以名稱來識別。 每個方法宣告都會指定每個參數的名稱、型別與類型(值、參考或輸出)以及回傳值。 .NET 類別庫會定義內建的數值類型和複雜型別,這些類型代表各種不同的建構。 這些結構包括檔案系統、網路連線、物件集合與陣列,以及日期。 典型的 C# 程式會使用類別庫的類型和使用者定義型別,以建立程式問題定義域專屬概念的模型。
C# 也支援 結構型態,如元組和匿名型。 結構型別 由每個成員的名稱與類型,以及表達式中成員的順序所定義。 結構類型沒有獨特的名稱。
儲存在資料類型中的資訊可以包含下列項目:
- 類型變數所需的儲存空間。
- 它可以表示的最大值和最小值。
- 包含的成員(方法、欄位、事件等等)。
- 其繼承自的基底類型。
- 它所實作的介面。
- 允許的作業。
編譯程式會使用類型資訊來確保程式代碼中執行的所有作業都是 類型安全。 例如,如果您宣告 類型的 int變數,編譯程式可讓您在加法和減法運算中使用 變數。 如果您嘗試在 類型的 bool變數上執行相同的作業,編譯程式會產生錯誤,如下列範例所示:
int a = 5;
int b = a + 2; //OK
bool test = true;
// Error. Operator '+' cannot be applied to operands of type 'int' and 'bool'.
int c = a + test;
備註
C 和 C++ 開發人員請注意,在 C# 中, bool 無法轉換為 int。
編譯程式會將類型資訊內嵌至可執行文件作為元數據。 Common Language Runtime (CLR) 會在運行時間使用該元數據,在配置和回收記憶體時進一步保證類型安全性。
在變數宣告中指定類型
當您在程式中宣告變數或常數時,您必須指定其類型或使用 var 關鍵詞,讓編譯程式推斷類型。 下列範例顯示一些使用內建數值類型和複雜使用者定義型別的變數宣告:
// Declaration only:
float temperature;
string name;
MyClass myClass;
// Declaration with initializers (four examples):
char firstLetter = 'C';
var limit = 3;
int[] source = [0, 1, 2, 3, 4, 5];
var query = from item in source
where item <= limit
select item;
你可以在方法宣告中指定方法參數和回傳值的類型。 下列簽章顯示需要 int 做為輸入自變數的方法,並傳回字串:
public string GetName(int ID)
{
if (ID < names.Length)
return names[ID];
else
return String.Empty;
}
private string[] names = ["Spencer", "Sally", "Doug"];
宣告變數後,你不能用新型別重新宣告它,也不能指派與該類型不相容的值。 例如,您無法宣告 int ,然後將布林值 true指派為 。 不過,你可以將值轉換成其他類型,例如指派給新變數或以方法參數傳遞。 編譯器會自動執行不會造成資料遺失的 型別轉換 。 可能導致數據遺失的轉換需要在原始程式碼中 轉換 。
如需詳細資訊,請參閱 轉換和類型轉換。
內建類型
C# 提供一組標準內建類型。 這些類型代表整數、浮點數值、布林運算式、文字字元、十進位值及其他類型的資料。 該語言也包含內建 string 的 和 object 型別。 這些類型可以用於任何 C# 程式。 完整內建類型列表,請參見 內建類型。
自訂類型
使用 元組 來儲存相關資料成員,以創建結構類型。 這類結構可容納多位成員。 元組的行為有限。 它們是數值的容器。 這些是你可以創造的最簡單類型。 你之後可能會決定需要一些行為。 在這種情況下,你可以將元組轉換成 struct 或 class。
使用 struct、 class、 interface、 enum和 record constructs 來建立你自己的自訂類型。 .NET 類別庫本身是自定義類型的集合,您可以在自己的應用程式中使用。 根據預設,類別庫中最常使用的型別可在任何 C# 程式中使用。 你可以透過明確地為提供這些類型的套件加入一個套件參考來讓它們可用。 編譯器取得套件的參考後,你可以在原始碼中宣告該套件組合中宣告的類型中的變數和常數。
您在定義類型時所做的第一個決策是決定要用於您類型的建構。 下列清單有助於做出該初始決策。 有些選擇是重疊的。 在大部分情況下,多個選項是合理的選擇。
- 如果資料型別不屬於你的應用程式領域,且不包含行為,請使用結構型態。
- 如果資料記憶體大小很小,則不超過 64 個字節,請選擇
struct或record struct。 - 如果類型不可變,或您想要不具破壞性的突變,請選擇
struct或record struct。 - 如果您的類型應具有值相等性語意,請選擇
record class或record struct。 - 若類型主要用於儲存資料且行為最小,則選擇 或
record classrecord struct。 - 如果類型是繼承階層的一部分,請選擇
record class或class。 - 如果類型使用多型,請選擇
class。 - 如果主要用途是行為,請選擇
class。
你也可以選擇一個 interface 來建模 契約:由成員描述的行為可以由不相關的類型來實作。 介面是抽象的,宣告的成員必須由繼承自該介面的所有class或struct類型來實作。
通用類型系統
共同型別系統支持繼承原則。 類型可以衍生自其他類型,稱為 基底類型。 衍生型別會繼承方法、屬性和基底型別的其他成員(有一些限制)。 基底類型可以接著衍生自某些其他類型,在此情況下,衍生類型會繼承其繼承階層中這兩個基底類型的成員。
所有類型,包括內建的數值類型( System.Int32 如 (C# 關鍵字:), int最終都源自單一基底類型,即 System.Object (C# 關鍵字: object)。 這種統一類型階層稱為 Common Type System (CTS)。 如需 C# 中繼承的詳細資訊,請參閱 繼承。
CTS 中的每個類型都會定義為 實值型 別或 參考型別。 這些類型包含 .NET 類別庫中的所有自訂類型,以及你自己的使用者定義類型:
- 你用
structORrecord struct關鍵字定義的類型就是價值類型。 所有內建的數值類型皆為structs。 - 你用
class、record class或record關鍵字定義的類型就是參考類型。
參考型態與值型別有不同的編譯時規則與執行時行為。
備註
最常用的類型皆在 System 命名空間中組織。 不過,包含類型的命名空間與它是值類型還是參考類型無關。
類別和結構是 .NET 中通用類型系統的兩個基本建構。 每個結構本質上都是一個資料結構,封裝了一組資料與行為,這些資料與行為應作為一個邏輯單元共同存在。 數據和行為是類別、結構或記錄 的成員 。 成員包含其方法、屬性、事件等等,如本文稍後所列。
類別、結構體或記錄宣告就像藍圖,你用它在執行時建立實例或物件。 如果您定義名為 Person 的類別、結構或記錄,Person 就是型別的名稱。 如果您宣告並初始化類型為 p的變數 Person,則 p 稱為 Person的物件或實例。 你可以建立多個相同 Person 類型的實例,每個實例的屬性和欄位值都可能不同。
類別是參考型別。 當你建立該類型的物件時,指派該物件的變數只會引用該記憶體。 當你將物件參考指派給新變數時,新變數也會指向原始物件。 你透過一個變數所做的變更會反映在另一個變數上,因為它們都指向相同的資料。
結構是實值型別。 當你建立一個結構體時,指派該結構體的變數會保留該結構的實際資料。 當你把結構體指派到新變數時,它會被複製。 因此,新的變數和原始變數包含兩個不同的相同數據複本。 你對一份的修改不會影響另一份。
記錄類型可以是參考型別(record class)或實值型別(record struct)。 記錄類型包含支援值相等的方法。
一般來說,使用類別來模擬更複雜的行為。 類別通常會儲存你在建立類別物件後修改的資料。 結構最適合小型數據結構。 結構體通常會儲存在建立完之後你不會修改的資料。 記錄類型是具有額外編譯器合成成員的資料結構。 記錄通常儲存的是物件建立後你不會修改的資料。
值類型
實值型別衍生自 System.ValueType,衍生自 System.Object。 衍生自 System.ValueType 的類型在 CLR 中具有特殊行為。 實值類型變數直接包含其值。 結構體的記憶體會在變數宣告的上下文中進行內嵌配置。 您可以宣告 record struct 實值型別的類型,並包含 記錄的合成成員。
存在兩種價值類型: struct 和 enum。
內建數值類型是結構,而且其具有您可以存取的欄位和方法:
// constant field on type byte.
byte b = byte.MaxValue;
但是,您會宣告值並將其指派給它們,就好像它們是簡單的非匯總類型:
byte num = 0xA;
int i = 5;
char c = 'Z';
值類型是封閉的。 你無法從任何值型態推導出型別,例如 System.Int32。 您無法定義要繼承自任何使用者定義類別或結構的結構,因為結構只能繼承自 System.ValueType。 不過,結構可以實作一或多個介面。 您可以將結構類型轉換成它實作的任何介面類型。 這個轉換會導致 boxing 作業將結構包裝在受控堆積上的參考類型物件中。 當您將值類型傳遞到以物件 (Object) 或任何介面類型做為輸入參數的方法時,就會發生裝箱 (Boxing) 操作。 如需詳細資訊,請參閱 Boxing 和 Unboxing。
使用 struct 關鍵字來建立你自己的自訂值型別。 一般而言,結構會作為一組小型相關變數的容器,如下列範例所示:
public struct Coords(int x, int y)
{
public int X { get; init; } = x;
public int Y { get; init; } = y;
}
如需結構的詳細資訊,請參閱 結構類型。 如需實值型別的詳細資訊,請參閱 實值型別。
實值型別的另一個類別是 enum。 列舉會定義一組具名整數常數。 例如,.NET 類別庫中的 System.IO.FileMode 列舉包含一組具名常數整數,指定檔案應該如何開啟。 其定義如下列範例所示:
public enum FileMode
{
CreateNew = 1,
Create = 2,
Open = 3,
OpenOrCreate = 4,
Truncate = 5,
Append = 6,
}
System.IO.FileMode.Create 常數的 值為2。 不過,此名稱對於讀取原始程式碼的人更有意義,因此最好使用列舉,而不是常數值。 如需詳細資訊,請參閱System.IO.FileMode。
所有列舉都繼承自 System.Enum,其繼承自 System.ValueType。 適用於結構的所有規則也同樣適用於列舉。 如需有關列舉的詳細資料,請參閱 列舉型別。
參考型別
你定義為 class、record class、record、delegate、陣列或 interface 的型別是 reference type。
當你宣告一個reference type類型的變數時,它會包含值null,直到你將它指派給該類型的實例物件,或用new運算子來建立一個實例。 以下範例示範了類別的建立與指派:
MyClass myClass = new();
MyClass myClass2 = myClass;
你不能直接用interface運算子來實例化new。 相反地,請建立並指派實作 介面之類別的實例。 請考慮下列範例:
MyClass myClass = new();
// Declare and assign using an existing value.
IMyInterface myInterface = myClass;
// Or create and assign a value in a single statement.
IMyInterface myInterface2 = new MyClass();
當你建立物件時,系統會在管理堆積中分配記憶體。 變數只保留物件位置的參考。 受控堆中的類型在配置和回收時都需要額外的開銷。 垃圾收集 是 CLR 的自動記憶體管理功能,可執行回收。 不過,垃圾收集也已高度優化,而且在大部分情況下,它不會建立效能問題。 如需垃圾收集的詳細資訊,請參閱 自動記憶體管理。
所有陣列都是參考型別,即使其元素是實值型別也一樣。 陣列會隱含衍生自 System.Array 類別。 你可以透過 C# 提供的簡化語法來宣告並使用它們,如下範例所示:
// Declare and initialize an array of integers.
int[] nums = [1, 2, 3, 4, 5];
// Access an instance property of System.Array.
int len = nums.Length;
參考型別完全支持繼承。 當您建立類別時,可以繼承自未定義為 密封的任何其他介面或類別。 其他類別可以繼承您的類別,並重寫您的虛擬方法。 如需如何建立您自己的類別的詳細資訊,請參閱 類別、結構及記錄。 如需繼承和虛擬方法的詳細資訊,請參閱 繼承。
字面值的類型
在 C# 中,編譯器會為字面值指派型別。 您可以在數字結尾附加字母,以指定數字常數應如何定義。 例如,若要指定值 4.56 應該視為 float,請在數位後面附加 「f」 或 「F」 : 4.56f。 如果你沒有附加字母,編譯器會推斷該字面值的型別。 關於可以用字母後綴指定哪些類型,請參見積分數值類型與浮點數值類型。
由於常值具有型別,且所有型別最終都源自System.Object,你可以編寫並編譯如下的程式碼:
string s = "The answer is " + 5.ToString();
// Outputs: "The answer is 5"
Console.WriteLine(s);
Type type = 12345.GetType();
// Outputs: "System.Int32"
Console.WriteLine(type);
泛型類型
宣告一個型態,並具有一個或多個型別參數,這些參數作為實際型態(具體型態)的佔位符。 用戶端程式代碼會在建立型別的實例時提供具體類型。 這些類型稱為 通用類型。 例如,.NET 型態 System.Collections.Generic.List<T> 有一個型態參數,依慣例命名 T為 。 當你建立該類型的實例時,你會指定該清單所包含物件的類型,例如 string:
List<string> stringList = new List<string>();
stringList.Add("String example");
// compile time error adding a type other than a string:
stringList.Add(4);
使用 type 參數可以重複使用同一類別來儲存任何類型的元素,而不必將每個元素轉換成 物件。 通用集合類別之所以是 強型別集合 ,是因為編譯器知道集合元素的特定型別,且在編譯時若你嘗試在前述範例中為物件加入整數 stringList ,可能會產生錯誤。 如需詳細資訊,請參閱 泛型。
元組與匿名型別
如果你不打算用公開 API 儲存或傳遞這些值,為簡單的相關值集合建立型別可能會很不方便。 你可以為此目的建立 元組 或匿名 型別 。 更多資訊請參見 元組 與 匿名型別。
可為 Null 的實值型別
一般實值型別不能有值為 null。 不過,您可以在型別後附加來建立?。 例如, int? 是一種 int 型別,也可以有 值 null。 可為 Null 的實值型別是泛型結構類型的 System.Nullable<T>實例。 當您將數據傳遞至及其數值可能為 Null 的資料庫時,具可為 Null 特性的值型別特別有用。 如需詳細資訊,請參閱 可為 Null 的實值型別。
隱含型別宣告
透過關鍵字 var 隱式輸入本地變數(但不輸入類別成員)。 變數在編譯時仍會接收型別,但編譯器會提供該型別。 如需詳細資訊,請參閱隱含型別區域變數。
編譯時間類型和運行時間類型
變數可以有不同的編譯時間和運行時間類型。 編譯時間類型是原始碼中變數的宣告或推斷類型。 運行時間類型是該變數所參考的實例類型。 這兩種類型通常相同,如下列範例所示:
string message = "This is a string of characters";
在其他情況下,編譯時間類型不同,如下列兩個範例所示:
object anotherMessage = "This is another string of characters";
IEnumerable<char> someCharacters = "abcdefghijklmnopqrstuvwxyz";
在上述兩個 string範例中,執行時間類型是 。 編譯時間類型位於 object 第一行和第 IEnumerable<char> 二行中。
如果變數的兩種類型不同,請務必瞭解何時套用編譯時間類型和運行時間類型。 編譯時型別決定編譯器所採取的所有動作。 這些編譯程式動作包括方法呼叫解析、多載解析,以及可用的隱含和明確轉換。 執行階段類型決定所有在執行階段解析的操作。 這些執行時期動作包括分派虛擬方法呼叫、評估is和switch之表達式,以及其他類型測試 API。 若要進一步瞭解您的程式代碼如何與類型互動,請辨識哪些動作會套用至哪一種類型。
相關區段
如需詳細資訊,請參閱下列文章:
C# 語言規格
如需詳細資訊,請參閱<C# 語言規格>。 語言規格是 C# 語法和使用方式的最終來源。