C# 是強型別語言。 每個變數和常數都有類型,評估為值的每個表達式也一樣。 每個方法宣告都會針對每個輸入參數和傳回值指定名稱、類型和種類(值、參考或輸出)。 .NET 類別庫會定義內建的數值類型和複雜型別,這些類型代表各種不同的建構。 其中包括文件系統、網路連線、物件的集合和數位,以及日期。 典型的 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
、interface
、enum
和 record
建構來建立您自己的自定義類型。 .NET 類別庫本身是自定義類型的集合,您可以在自己的應用程式中使用。 根據預設,類別庫中最常使用的型別可在任何 C# 程式中使用。 只有當您明確將項目參考新增至定義這些專案的元件時,其他專案才會變成可用。 編譯程式具有元件的參考之後,您可以在原始程式碼中宣告該元件中宣告之類型的變數(和常數)。 如需詳細資訊,請參閱 .NET 類別庫。
您在定義類型時所做的第一個決策是決定要用於您類型的建構。 下列清單有助於做出該初始決策。 這些選擇中有重疊。 在大部分情況下,多個選項是合理的選擇。
- 如果資料記憶體大小很小,則不超過 64 個字節,請選擇
struct
或record struct
。 - 如果類型不可變,或您想要不具破壞性的突變,請選擇
struct
或record struct
。 - 如果您的類型應具有值相等性語意,請選擇
record class
或record struct
。 - 如果類型主要用於儲存資料,而非行為,請選擇
record class
或record struct
。 - 如果類型是繼承階層的一部分,請選擇
record class
或class
。 - 如果類型使用多型,請選擇
class
。 - 如果主要用途是行為,請選擇
class
。
通用類型系統
請務必瞭解 .NET 中類型系統的兩個基本點:
- 它支援繼承原則。 類型可以衍生自其他類型,稱為 基底類型。 衍生型別會繼承方法、屬性和基底型別的其他成員(有一些限制)。 基底類型可以接著衍生自某些其他類型,在此情況下,衍生類型會繼承其繼承階層中這兩個基底類型的成員。 所有類型,包括內建的數值類型,例如 System.Int32 (C# 關鍵詞:
int
),最終衍生自單一基底類型,也就是 System.Object (C# 關鍵詞:object
。 這種統一類型階層稱為 Common Type System (CTS)。 如需 C# 中繼承的詳細資訊,請參閱 繼承。 - CTS 中的每個類型都會定義為 實值型 別或 參考型別。 這些類型包括 .NET 類別庫中的所有自定義類型,以及您自己的使用者定義型別。 您使用
struct
關鍵字定義的類型是實值型別;所有內建的數值類型都是structs
。 您使用class
或record
關鍵字所定義的類型是參考型別。 參考類型和實值型別有不同的編譯時間規則,以及不同的運行時間行為。
下圖顯示CTS中實值型別與參考型別之間的關聯性。
備註
您可以看到最常用的類型全都組織在 命名空間中 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
{
public int x, y;
public Coords(int p1, int p2)
{
x = p1;
y = p2;
}
}
如需結構的詳細資訊,請參閱 結構類型。 如需實值型別的詳細資訊,請參閱 實值型別。
實值型別的另一個類別是 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
、delegate
、陣列或 interface
的類型是 reference type
。
當您宣告一個reference type
變數時,它會包含值null
,直到您使用該類型的實例來指派它,或使用new
運算符來建立一個。 下列範例示範類別的建立和指派:
MyClass myClass = new MyClass();
MyClass myClass2 = myClass;
無法使用interface
運算子直接具現化new
。 相反地,請建立並指派實作 介面之類別的實例。 請考慮下列範例:
MyClass myClass = new MyClass();
// 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
加入至上一個範例中的物件,則可以在編譯時期引發錯誤。 如需詳細資訊,請參閱 泛型。
隱含型別、匿名型別和可為 Null 的值類型
您可以使用 關鍵詞隱含地輸入局部變數(但不能是類別成員 var
)。 變數仍會在編譯階段接收類型,但此類型是由編譯程式提供。 如需詳細資訊,請參閱隱含型別區域變數。
對於您不打算儲存或傳遞外部方法界限之簡單相關值的簡單集合,建立具名類型可能不方便。 您可以為此目的建立 匿名類型 。 如需詳細資訊,請參閱 匿名型別。
一般實值型別不能有值為 null
。 不過,您可以在型別後附加來建立?
。 例如, int?
是一種 int
型別,也可以有 值 null
。 可為 Null 的實值型別是泛型結構類型的 System.Nullable<T>實例。 當您將數據傳遞至及其數值可能為 Null 的資料庫時,具可為 Null 特性的值型別特別有用。 如需詳細資訊,請參閱 可為 Null 的實值型別。
編譯時間類型和運行時間類型
變數可以有不同的編譯時間和運行時間類型。 編譯時間類型是原始碼中變數的宣告或推斷類型。 運行時間類型是該變數所參考的實例類型。 這兩種類型通常相同,如下列範例所示:
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# 語法和使用方式的最終來源。