型別 (C# 程式設計手冊)
型別、變數和值
C# 是一種強型別 (Strongly Typed) 語言。 任何變數和常數都有一個型別,與所有運算式都會評估為某個值一樣。 每個方法簽章都會為每個輸入參數和傳回值指定型別。 .NET Framework 類別庫 (Class Library) 定義了一組內建的數字型別 (Numeric Type) 和更複雜的型別,以表示各類邏輯建構,例如檔案系統、網路連線、集合物件和陣列物件,以及日期。 典型的 C# 程式會使用類別庫的型別和使用者定義的型別,針對程式之問題網域的特定概念建立模型。
型別中儲存的資訊可以包含下列各項:
型別的變數所需的儲存空間。
它可以表示的最大值和最小值。
其中內含的成員 (方法、欄位、事件等等)。
其繼承的基底型別 (Base Type)。
於執行階段將配置之變數的記憶體位置。
允許的作業種類。
編譯器 (Compiler) 會使用型別資訊,確認程式碼中執行的所有作業是否符合「型別安全」(Type Safe)。 例如,如果變數的型別宣告為 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;
注意事項 |
---|
編譯器會將該型別資訊內嵌到可執行檔中,做為中繼資料 (Metadata)。 Common Language Runtime (CLR) 在執行階段會使用該中繼資料,進一步確保配置和回收記憶體時的型別安全。
在變數宣告中指定型別
在程式中宣告變數或常數時,必須指定其型別,或者是使用 var 關鍵字讓編譯器推斷型別。 下列範例顯示的部分變數宣告,會使用內建數字型別 (Numeric Type) 以及複雜的使用者定義型別:
// 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 的布林值指派給它。 然而,值可以轉換為其他型別,例如,為這些值指派新變數,或將這些值當做方法的引數傳遞。 編譯器會自動執行不會導致資料遺失的「型別轉換」(Type Conversion)。 可能會導致資料遺失的轉換需要在原始程式碼中使用「轉型」(Cast)。
如需詳細資訊,請參閱轉型和型別轉換 (C# 程式設計手冊)。
內建型別
C# 提供一組標準的內建數字型別,以表示整數、浮點數、布林運算式、文字字元、十進位值和其他資料型別。 同時也有內建的 string 和 object 型別。 您可以在任何 C# 程式中使用這些項目。 如需內建型別的詳細資訊,請參閱型別參考表 (C# 參考)。
自訂型別
您可以使用結構、類別、介面和列舉建構函式建立自己的自訂型別。 .NET Framework 類別庫本身是由 Microsoft 所提供的自訂型別集合,您可以用於自己的應用程式中。 根據預設,您可以在任何 C# 程式中使用類別庫內最常用到的型別。 對於其他型別,則只有在明確加入組件 (其中已定義這些型別) 的專案參考時,才可以使用這些型別。 在編譯器具有組件的參考後,您就可以在原始程式碼中,宣告組件中已宣告之型別的變數 (和常數)。 如需詳細資訊,請參閱.NET Framework 類別庫。
一般型別系統
了解 .NET Framework 中型別系統的兩個基本要點是非常重要的:
它支援繼承 (Inheritance) 的準則。 型別可以衍生自其他型別 (稱為「基底型別」(Base Type))。 衍生的型別會繼承基底型別的方法、屬性和其他成員,但是具有某些限制。 反之,基底型別可以衍生自某些其他型別,在這種情況下,衍生的型別會在其繼承階層架構 (Inheritance Hierarchy) 中,繼承兩個基底型別的成員。 包括如 System.Int32 (C# 關鍵字:int) 這類的內建數字型別,所有的型別最終都是衍生自單一基底型別 (Base Type),也就是 System.Object (C# 關鍵字:object)。 這個共同的型別階層架構稱為一般型別系統 (CTS)。 如需 C# 中繼承的詳細資訊,請參閱繼承 (C# 程式設計手冊)。
CTS 中的每個型別都是以「實值型別」(Value Type) 或「參考型別」(Reference Type) 定義的。 這包含 .NET Framework 類別庫中所有的自訂型別,同時也包括您自己的使用者定義型別。 使用 struct 關鍵字定義的型別是實值型別,而所有內建的數字型別是 structs。 使用 class 關鍵字定義的型別是參考型別。 參考型別和實值型別各具有不同的編譯時期規則和不同的執行階段行為。
下圖顯示 CTS 中實值型別和參考型別之間的關係。
CTS 中的實值型別和參考型別
注意事項 |
---|
您可以看到最常使用的型別全都已經整理在 System 命名空間中。 然而,包含型別的命名空間,與其是否為實值型別和參考型別卻沒有關係。 |
實值型別
實值型別衍生自 System.ValueType,而該型別則衍生自 System.Object。 衍生自 System.ValueType 的型別,在 CLR 具有特別的行為。 實值型別變數直接包含其值,這表示在宣告任何內容的變數時,便會內嵌 (Inline) 配置記憶體。 沒有針對實值型別變數進行個別的堆積配置 (Heap Allocation) 或負荷記憶體回收。
內建的數字型別 (Numeric Type) 都是結構 (Struct),它們具有您可以存取的屬性和方法:
// Static method on type Byte.
byte b = Byte.MaxValue;
不過您要宣告及指派值給它們,就像它們是簡單的非彙總 (Aggregate) 型別一樣:
byte num = 0xA;
int i = 5;
char c = 'Z';
實值型別是「密封」(Sealed) 的型別,舉例說明其意義,就是型別不能衍生自 System.Int32,以及結構不能繼承自任何使用者定義的類別或結構,因為結構只能繼承自 System.ValueType。 然而,結構可以實作一個或多個介面。 結構型別可以轉型為介面型別,這會導致 Boxing 作業執行,將結構包裝在 Managed 堆積 (Heap) 上的參考型別物件內。 將實值型別傳遞給採用 System.Object 做為輸入參數的方法時,會發生 Boxing 作業。 如需詳細資訊,請參閱Boxing 和 Unboxing (C# 程式設計手冊)。
您可以使用 struct 關鍵字,建立專屬自訂實值型別。 結構通常都是用來做為一小組相關變數的容器,如下列範例所示:
public struct CoOrds
{
public int x, y;
public CoOrds(int p1, int p2)
{
x = p1;
y = p2;
}
}
如需結構的詳細資訊,請參閱結構 (C# 程式設計手冊)。 如需 .NET Framework 中實值型別的詳細資訊,請參閱一般型別系統。
實值型別的另一個分類是列舉。 列舉可以定義一組具名的整數常數。 例如,.NET Framework 類別庫中的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。 適用於結構的所有規則也適用於列舉。 如需列舉的詳細資訊,請參閱列舉型別 (C# 程式設計手冊)。
參考型別
定義為類別、委派、陣列或介面的型別為「參考型別」(Reference Type)。 將變數宣告為參考型別時,變數在執行階段的一開始會包含 null 值,直到您使用 new 運算子明確建立物件的執行個體,或為其指派使用 new, as shown in the following example: 在其他地方所建立的物件為止,如下列範例所示:
MyClass mc = new MyClass();
MyClass mc2 = mc;
介面必須與實作該介面的類別物件一併初始化。 如果 MyClass 實作 IMyInterface,就表示您建立了 IMyInterface 的執行個體,如下列範例所示:
IMyInterface iface = new MyClass();
建立物件時會將記憶體配置在 Managed 堆積上,而變數僅會持有物件的位置參考。 Managed 堆積上的型別在這兩種情況下都需要額外負荷,在由 CLR 自動記憶體管理功能進行配置時和回收時,後者即稱為「記憶體回收」(Garbage Collection)。 然而,記憶體回收也已經過高度最佳化,而且在大部分的案例中都不會建立效能問題。 如需記憶體回收的詳細資訊,請參閱自動記憶體管理。
所有的陣列都是參考型別,即使其元素為實值型別也一樣。 陣列是隱含衍生自 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# 程式設計手冊)。 如需繼承和虛擬方法的詳細資訊,請參閱繼承 (C# 程式設計手冊)。
常值的型別
C# 中的常值會從編譯器接收型別。 您可以指定該如何套用數值常值的型別,方法是在數值尾端附加一個字母。 例如,若要指定將值 4.56 做為浮點數處理,則在數值後附加 "f" 或 "F":4.56f。 如果沒有附加字母,編譯器就會推斷常值的型別。 如需哪些型別可以藉由使用字母後置字元進行指定的詳細資訊,請參閱實值型別 (C# 參考) 中個別型別的參考頁。
因為常值具有型別,而所有的型別最終都衍生自 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);
泛型型別
您可以使用一個或多個「型別參數」(Type Parameter) 做為實際型別的替代符號 (「具象型別」(Concrete Type)) 來宣告型別,用戶端程式碼將會在建立型別的執行個體時提供這些替代符號。 此類型別稱為「泛型型別」(Generic Type)。 例如,.NET Framework 型別 System.Collections.Generic.List<T> 具有一個型別參數,按照慣例,會命名為 T。 當您建立該型別的執行個體時,就要指定清單中內含的物件型別,例如字串。
List<string> strings = new List<string>();
如果使用型別參數,便能重複使用相同類別來保存任何元素的型別,而不需要將每個項目都轉換為物件。 泛型集合類別稱為「強型別集合」(Strongly-Typed Collection),因為編譯器知道集合元素的特定型別。而且,假如您嘗試在上一個範例中將整數加入至 strings 物件,便可能會在編譯時期引發編譯器錯誤。 如需詳細資訊,請參閱泛型 (C# 程式設計手冊)。
隱含型別、匿名型別和可為 Null 的型別
如先前所述,您可以藉由使用 var 關鍵字,隱含套用區域變數 (但非類別成員) 的型別。 變數仍然會在編譯時期接收到型別,但型別是由編譯器所提供的。 如需詳細資訊,請參閱隱含型別區域變數 (C# 程式設計手冊)。
在有些案例中,對於沒有計劃要在方法界限外儲存或傳遞的一組簡單相關值而言,為其建立具名型別並不方便。 針對這個目的,您可以建立「匿名型別」(Anonymous Type)。 如需詳細資訊,請參閱匿名型別 (C# 程式設計手冊)。
一般實值型別的值不能為 null。 然而藉由在型別後附加 ?,即可以建立可為 Null 的實值型別。 例如,int? 這種 int 型別的值也可以是 null。 在 CTS 中,可為 Null 的型別都是泛型結構型別為 System.Nullable<T> 的執行個體。 當資料庫中的數值可能為 null 時,在與該資料庫進行資料來回傳遞時,可為 Null 的型別就特別地好用。 如需詳細資訊,請參閱可為 Null 的型別 (C# 程式設計手冊)。
相關章節
如需詳細資訊,請參閱下列主題:
C# 語言規格
如需詳細資訊,請參閱 C# 語言規格。 語言規格是 C# 語法和用法的決定性來源。