C# 語言教學導覽

C# (發音為 "See Sharp") 是物件導向、型別安全的現代化程式設計語言。 C# 可讓開發人員建置許多在 .NET 中執行的安全且健全的應用程式型別。 C# 源自於是 C 系列語言,使用 C、C++、Java 和 JavaScript 的程式設計人員會立即感到熟悉。 此導覽提供在 C# 11 或更舊版本中語言主要元件的概觀。 若要透過互動式範例探索語言,請嘗試 C# 簡介教學課程。

C# 是物件導向、元件導向的程式設計語言。 C# 提供語言建構來直接支援這些概念,讓 C# 自然而然成為建立及使用軟體元件的語言。 自其來源起,C# 已新增功能來支援新的工作負載和新興軟體設計做法。 C# 的核心是物件導向的語言。 您可以定義型別及其行為。

數個 C# 功能可協助建立健全且耐久的應用程式。 記憶體回收會自動回收無法連線的未使用物件所佔用的記憶體。 可為 Null 的型別可防止未參考已配置物件的變數。 例外狀況處理提供結構化且可延伸的錯誤偵測和復原方法。 Lambda 運算式支援功能性程式設計技術。 語言整合式查詢 (LINQ) 語法會建立使用任何來源資料的常見模式。 非同步作業的語言支援提供建置分散式系統的語法。 C# 有統一的型別系統。 所有的 C# 型別 (包括 intdouble 等基本型別) 都繼承自單一的 object 根型別。 所有型別都會共用一組常見的作業。 任何型別值的儲存、傳送及操作方式都是一致的。 此外,C# 同時支援使用者定義的參考型別實值型別。 C# 允許動態配置物件和輕量型結構的內嵌儲存。 C# 支援泛型方法和型別,以提供更高的型別安全性和效能。 C# 提供列舉程式,可讓集合類別的實作者定義用戶端程式碼的自訂行為。

C# 強調版本控制,以確保程式和程式庫可以隨著時間以相容的方式演進。 直接受版本控制考量影響的 C# 設計層面包括個別的 virtualoverride 修飾元、方法多載解析的規則,以及對明確介面成員宣告的支援。

.NET 架構

C# 程式在 .NET 上執行,是名為通用語言執行平台 (CLR) 的虛擬執行系統和一組類別庫。 CLR 是由通用語言基礎結構 (CLI) 的 Microsoft 實作,通用語言基礎結構 (CLI) 是國際標準。 CLI 是建立執行和開發環境的基礎,其中語言和程式庫可以順暢地搭配運作。

以 C# 撰寫的原始程式碼會編譯成符合 CLI 規格的中繼語言 (IL)。 IL 程式碼和像是點陣圖和字串的資源,會儲存在組件中,副檔名通常為 .dll。 組件包含的資訊清單提供有關組件的型別、版本、文化特性 (Culture) 的資訊。

執行 C# 程式時,會將組件載入至 CLR。 CLR 會執行 Just-In-Time (JIT) 編譯,以將 IL 程式碼轉換成原生機器指令。 CLR 也提供有關自動記憶體回收、例外狀況處理和資源管理的其他服務。 由 CLR 執行的程式碼有時稱為「受控程式碼」,會將非受控程式碼編譯成以特定平台為目標的原生機器語言。

語言互通性是 .NET 的一項重要功能。 C# 編譯器所產生的 IL 程式碼符合一般型別系統 (CTS)。 從 C# 產生的 IL 程式碼可以與從 F#、Visual Basic、C++ 的 .NET 版本產生的程式碼互動。 有 20 個以上的符合 CTS 規範的語言。 單一組件可能包含以不同 .NET 語言撰寫的多個模組。 型別可以彼此參考,就像是以相同語言撰寫一樣。

除了執行階段服務之外,.NET 也包含廣泛的程式庫。 這些程式庫支援許多不同的工作負載。 其會組織成提供各種實用功能的命名空間。 程式庫包含從檔案輸入和輸出到字串操作到 XML 剖析、Web 應用程式架構到 Windows Forms 控制項的所有項目。 一般 C# 應用程式會使用廣泛用來處理常見「配管」例行工作的 .NET 類別庫。

如需 .NET 的詳細資訊,請參閱 .NET概觀

Hello World

“Hello, World” 程式通常用來介紹程式設計語言。 以下是以 C# 撰寫的:

using System;

class Hello
{
    static void Main()
    {
        // This line prints "Hello, World" 
        Console.WriteLine("Hello, World");
    }
}

“Hello, World” 程式的開頭為 using 指示詞,會參考 System 命名空間。 命名空間提供組織 C# 程式和程式庫的階層式方法。 命名空間包含型別和其他命名空間,例如 System 命名空間包含數個型別 (如程式中參考的 Console 類別),和許多其他命名空間 (如 IOCollections)。 使用 using 指示詞參考指定的命名空間,就能以非限定的方式使用屬於該命名空間成員的型別。 因為 using 指示詞的緣故,該程式可以使用 Console.WriteLine 當作 System.Console.WriteLine 的縮寫。

“Hello, World” 程式宣告的 Hello 類別包含單一成員,即名為 Main 的方法。 Main 方法的宣告是使用 static 修飾元來進行。 執行個體方法可以使用關鍵字 this 參考特定的封入物件執行個體,但靜態方法卻不需要參考特定物件即可運作。 依照慣例,名為 Main 的靜態方法會做為 C# 程式的進入點。

開頭為 // 的行是單行註解。 C# 單行註解開頭為 //,且繼續到目前行的結尾。 C# 也支援多行註解。 多行註解開頭為 /*,結尾為 */。 程式的輸出是由 System 命名空間中 Console 類別的 WriteLine 方法產生。 此類別是由標準類別程式庫提供,根據預設,編譯器會自動參考此程式庫。

您可以使用 .NET SDK 來建置您自己的 "Hello, World" 程式。 安裝 SDK 之後,您可以執行 dotnet new console 來建立可修改的基本 "Hello, World" 程式。 如需詳細資訊,請參閱 .NET 新手入門一節中的 Hello, World 教學課程

型別與變數

型別會定義 C# 中任何資料的結構和行為。 型別的宣告可能包含其成員、基底類型、其實作的介面,以及該型別允許的作業。 變數是參考特定型別執行個體的標籤。

C# 中有兩種型別:實值型別參考型別。 實值型別的變數則會直接包含其值。 參考型別的變數會將參考儲存到其資料,後者即是物件。 使用參考型別時,這兩種變數可以參考相同的物件,因此對其中一個變數進行的作業可能會影響另一個變數所參考的物件。 使用實值型別時,變數各有自己的資料複本,因此在某一個變數上進行的作業不可能會影響其他變數 (但 refout 參數變數除外)。

識別碼是變數名稱。 識別碼是一連串的 Unicode 字元,不含任何空白字元。 如果識別碼的前面加上 @,則識別碼可能是 C# 保留字。 與其他語言互動時,使用保留字做為識別碼可能很有用。

C# 的實值型別可進一步細分為簡單型別列舉型別結構型別可為 Null 的實值型別元組實值型別。 C# 的參考型別可進一步細分為類別型別介面型別陣列型別委派型別

以下大綱提供 C# 的型別系統概觀。

C# 程式使用型別宣告來建立新型別。 型別宣告指定新型別的名稱成員。 可由使用者定義的六種 C# 型別︰類別型別、結構型別、介面型別、列舉型別、委派型別和元組實值型別。 您也可以宣告 record 型別,也就是 record structrecord class。 記錄型別具有編譯器合成成員。 您主要使用記錄來儲存值,相關的行為很少。

  • class 型別定義資料結構,其中包含資料成員 (欄位) 和函式成員 (方法、屬性及其他)。 類別型別支援單一繼承和多型,這些是可供衍生類別將基底類別延伸及特製化的機制。
  • struct 型別與類別型別相似,它代表具有資料成員和函式成員的結構。 不過,不同於類別,結構是實值型別,而且通常不需要堆積配置。 結構型別不支援使用者指定的繼承,且所有結構型別都隱含地繼承自 object 型別。
  • interface 型別將合約定義為一組具名的公用成員。 實作 interfaceclassstruct 必須提供介面成員的實作。 interface 可以繼承自多個基底介面,classstruct可實作多個介面。
  • delegate 型別代表對方法的參考,其中含有特定參數清單與傳回型別。 委派讓您可將方法視為實體,而實體能指派給變數或當作參數來傳遞。 委派類似函式語言提供的函式型別。 其也類似於某些其他語言中找到的函數指標概念。 與函數指標不同之處在於,委派是物件導向且型別安全。

classstructinterfacedelegate 型別全都支援泛型,因此其可以使用其他型別來參數化。

C# 支援任何型別的單一維度及多維陣列。 與上述型別不同,陣列型別不需宣告即可使用。 而陣列型別的建構方法,是在型別名稱之後加上方括弧。 例如,int[]int 的單一維度陣列、int[,]int 的雙維陣列,而 int[][]int 的單一維度陣列的單一維度陣列或是「不規則」陣列。

可為 Null 的型別不需要個別的定義。 針對每個不可為 Null 的型別 T,會有一個對應的可為 Null 型別 T?,其中可包含一個額外值 null。 例如,int? 是可以保存任何 32 位元整數或值的 null 型別,而且 string? 是可以保存任何 string 或值的 null 型別。

C# 的型別系統已整合,任何型別值均可視為 object。 C# 中的每個型別都直接或間接衍生自 object 類別型別,而 object 是所有型別的基底類別。 參考型別的值之所以會視為物件,只是將這些值當作 object 型別來檢視。 數值型別的值之所以會視為物件,只是透過執行 boxingunboxing 作業。 在下列範例中,int 值會轉換成 object,並再次轉換回 int

int i = 123;
object o = i;    // Boxing
int j = (int)o;  // Unboxing

將實值型別的值指派給 object 參考時,會配置「方塊」來保存值。 該方塊是參考型別的執行個體,且該值會複製到該方塊中。 相反地,當 object 參考轉換為實值型別時,會檢查參考的 object 是否為正確實值型別的方塊。 如果檢查成功,則會將方塊中的複製至實值型別。

C# 的整合型別系統實際上表示實值型別會被視為 object「隨選」參考。由於整合,使用型別 object 的一般用途程式庫可以與衍生自 object 的所有型別搭配使用,包括參考型別和實值型別。

C# 中有數種變數,包括欄位、陣列元素、區域變數和參數。 變數代表儲存位置。 如下所示,每個變數都具有型別,該型別決定該變數可以儲存什麼樣的值。

  • 不可為 Null 的實值型別
    • 該型別的值
  • 可為 Null 的實值型別
    • null 值或該型別的值
  • 物件 (object)
    • null 參考、任一參考型別之物件的參考,或是任一實值型別的 Boxed 值的參考
  • 類別型別
    • null 參考、該類別型別之執行個體的參考,或衍生自該類別型別之類別執行個體的參考
  • 介面類型
    • null 參考、實作該介面型別之類別型別的執行個體的參考,或實作該介面型別之實值型別的 Boxed 值的參考
  • 陣列型別
    • null 參考、該陣列型別之執行個體的參考,或相容的陣列型別之執行個體的參考
  • 委派類型
    • null 參考或相容的委派類型之執行個體的參考

程式結構

C# 的重要組織概念如下:程式命名空間型別成員組件。 程式宣告型別,其中包含成員並可以依據命名空間分組。 類別、結構和介面都是型別的範例。 欄位、方法、屬性及事件都是成員的範例。 在編譯 C# 程式時,會將其實際封裝為組件。 組件通常具有副檔名 .exe.dll,這取決是其實作的分別是「應用程式」還是「程式庫」

以一個較小的範例為例,請參考下列 C# 程序碼:

namespace Acme.Collections;

public class Stack<T>
{
    Entry _top;

    public void Push(T data)
    {
        _top = new Entry(_top, data);
    }

    public T Pop()
    {
        if (_top == null)
        {
            throw new InvalidOperationException();
        }
        T result = _top.Data;
        _top = _top.Next;

        return result;
    }

    class Entry
    {
        public Entry Next { get; set; }
        public T Data { get; set; }

        public Entry(Entry next, T data)
        {
            Next = next;
            Data = data;
        }
    }
}

此類別的完整名稱是 Acme.Collections.Stack。 該類別包含數個成員︰一個名為 _top 的欄位、兩個名為 PushPop 的方法以及名為 Entry 的巢狀類別。 Entry 類別更包含三個成員︰名為 Next 的屬性、名為 Data 的屬性以及建構函式。 Stack 是泛型類別。 其有一個型別參數,T 會在使用時取代為具象型別。

堆疊是「先進先出」FILO 集合。 新增至堆疊頂端的新元素。 移除元素時,會將該元素從堆疊頂端移除。 上一個範例會宣告定義堆疊儲存體和行為的 Stack 型別。 您可以宣告參考要使用該功能之 Stack 型別執行個體的變數。

組件包含的可執行程式碼採用中繼語言 (IL) 指令形式,而符號資訊採用中繼資料形式。 執行之前,.NET 通用語言執行平台的 Just-In-Time (JIT) 編譯器會將組件中的 IL 程式碼轉換為處理器特定的程式碼。

由於組件是包含程式碼和中繼資料的功能自我描述單位,所以不需要提供 C# 中的 #include 指示詞和標頭檔。 特定組件中所包含的公用型別和成員只能在編譯程式時,使用 C# 程式來參考該組件。 例如,此程式使用來自 acme.dll 組件的 Acme.Collections.Stack 類別︰

class Example
{
    public static void Main()
    {
        var s = new Acme.Collections.Stack<int>();
        s.Push(1); // stack contains 1
        s.Push(10); // stack contains 1, 10
        s.Push(100); // stack contains 1, 10, 100
        Console.WriteLine(s.Pop()); // stack contains 1, 10
        Console.WriteLine(s.Pop()); // stack contains 1
        Console.WriteLine(s.Pop()); // stack is empty
    }
}

若要編譯此程式,您必須參考包含先前範例中所定義堆疊類別的組件。

C# 程式可以儲存在數個來源檔案中。 編譯 C# 程式時,所有來源檔案都會一起處理,而且來源檔案可以自由地相互參考。 就概念上而言,就像所有來源檔案在處理之前串連成一個大型檔案一樣。 C# 中不再需要向前宣告,原因是在少數的例外狀況中,宣告順序並不重要。 C# 不會限制來源檔案只能宣告一個公用型別,也不會要求來源檔案符合來源檔案中所宣告的型別名稱。

本導覽中的進一步文章會說明這些組織區塊。