C# 類型系統

小提示

剛開始開發軟體嗎? 先從 入門 教學開始。 他們會帶你了解編寫程式,並在學習過程中介紹各種類型。

有其他語言的經驗嗎? 如果你已經了解型別系統,可以快速瀏覽 值與參考 的區分,以及選擇適合的型別指南,接著閱讀特定型別的文章。

C# 是強型別語言。 每個變數、常數和表達式都有一個型別。 編譯器會透過檢查程式碼中的每個操作是否對相關型別有效來強制型 別安全 。 例如,你可以加上兩個 int 值,但不能加上一個 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

型別安全機制在編譯時偵測錯誤,確保在程式碼執行前解決。 編譯器也會將型別資訊嵌入執行檔中作為元資料,通用語言執行時(CLR)會利用這些資訊在執行時進行額外的安全檢查。

宣告帶有型態的變數

當你宣告變數時,你要明確指定其型別,或用來 var 讓編譯器從指定值推斷出該型別:

// Explicit type:
int count = 10;
double temperature = 36.6;

// Compiler-inferred type:
var name = "C#";
var items = new List<string> { "one", "two", "three" };

方法參數與回傳值也有型別。 以下方法接收stringint,回傳string:

static string GetGreeting(string name, int visitCount)
{
    return visitCount switch
    {
        1 => $"Welcome, {name}!",
        _ => $"Welcome back, {name}! Visit #{visitCount}."
    };
}

宣告變數後,你無法更改它的型別或指派與該型別不相容的值。 你可以將數值轉換成其他類型。 編譯器會執行隱 含轉換 ,不會自動遺失資料。 顯式轉換(cast)需要你在程式碼中標示轉換。 更多資訊請參見 選角與類型轉換

內建類型與自訂類型

C# 提供常見資料的 內建型別 :整數、浮點數、 boolcharstring。 每個 C# 程式都能使用這些內建型別,無需額外參考。

除了內建型別外,你還可以使用多種結構來創造自己的型別:

  • 類別 — 用於建模行為與複雜物件的參考類型。 支援繼承與多態性。
  • 結構體 — 用於小型、輕量級資料的值型別。 每個變數都有自己的副本。
  • 記錄 — 具有編譯器產生等號、 ToString且透過 with 表達式進行非破壞性變異的類別或結構體。
  • 介面 — 定義成員的合約,任何類別或結構體都能實作。
  • 列舉 — 命名的整數常數集合,例如星期幾或檔案存取模式。
  • 元組 — 輕量級結構型別,將相關值分組,但未定義命名型別。
  • Generics — 像 List<T>Dictionary<TKey, TValue> 這樣的型別參數化結構,提供型別安全,同時重複使用相同邏輯用於不同型別。

值型態與參考型別

C# 中的每個型態要麼是 值型態 ,要麼是 參考型態。 這種區別決定了變數如何儲存資料以及指派的運作方式。

值類型 直接持有其資料。 當你為新變數指派值型別時,執行時會複製資料。 一個變數的變動不會影響另一個。 結構、列舉以及內建的數值型別都是值型別。

參考型別 會保留對受管理堆積物件的參考。 當你為新變數指派參考型別時,兩個變數都會指向同一個物件。 透過一個變數的變化,另一個變數的變化即可見。 類別、陣列、代理和字串都是參考型別。

以下範例說明了兩者的差異。 第一個區塊顯示了記錄結構的定義 Coords ,這是一種值型別。 第二個區塊展示了值型別與參考型別的不同行為。

public readonly record struct Coords(int X, int Y);
// Value type: each variable holds its own copy
var point1 = new Coords(3, 4);
var point2 = point1;
Console.WriteLine($"point1: ({point1.X}, {point1.Y})");
Console.WriteLine($"point2: ({point2.X}, {point2.Y})");
// point1 and point2 are independent copies

// Reference type: both variables refer to the same object
var list1 = new List<int> { 1, 2, 3 };
var list2 = list1;
list2.Add(4);
Console.WriteLine($"list1 count: {list1.Count}"); // 4 — same object

所有類型最終都源自 System.Object。 值類型派生自 System.ValueType,而 System.ValueType 又派生自 。 這個統一的階層結構稱為 通用型別系統 (CTS)。 欲了解更多關於繼承的資訊,請參見 繼承

選擇哪種類型

當你定義一個新型別時,你選擇的類型會影響程式碼的行為。 請參考以下指引來做出初步決定:

  • 元組 — 暫時的值群組,不需要命名型別或行為。
  • structrecord struct — 小資料(約64位元組或以下)、值語意或不可變性。 記錄結構則增加了基於值的等式與 with 表達式。
  • record class — 以資料為主,具有基於值的相等性,ToString且具有非破壞性變更。 支持繼承。
  • class — 複雜行為、多態性或可變狀態。 大多數自訂類型都是類別。
  • interface — 一個非相關類型可以實作的契約。 定義的是能力,而非身份認同。
  • enum — 一組固定的命名常數,例如狀態碼或選項。

多於一個選項通常是合理的。

編譯時間類型和運行時間類型

變數在編譯時和執行時可以有不同的型別。 編譯時期型別是原始碼中宣告或推斷的型別。 執行時型別是變數所指的實例的實際型態。 執行時型別必須與編譯時型別相同,或是從編譯型態衍生或實作該型別的型別。 指派僅在存在從執行時型別隱含轉換到編譯時型別時有效,例如身份轉換、參考、盒裝或數值轉換。

// Compile-time and run-time types match:
string message = "Hello, world!";

// Compile-time type differs from run-time type:
object boxed = "This is a string at run time";
IEnumerable<char> characters = "abcdefghijklmnopqrstuvwxyz";

在前述範例中,boxed 的編譯時型態是 object,而執行時型態是 string。 此賦值之所以成立,是因為 string 衍生自 object。 同樣地, characters 具有編譯時類型 , IEnumerable<char>且 指派之所以有效,是因為 string 實作了該介面。 編譯時型別控制過載解決與可用轉換。 執行時型別控制虛擬方法調度、is表達式與switch表達式。

另請參閱

C# 語言規格

如需詳細資訊,請參閱<C# 語言規格>。 語言規格是 C# 語法和使用方式的最終來源。