C♯语法

**C Sharp 語法**是編寫該語言程序的一套規則。也適用於.NET Framework and Mono。

## C# 2.0 的特性

針對於.NET SDK 2.0（相對應於 ECMA-334 標準第三版），C# 的新特性有：

### 分部類別

分部類別將類別的實現分在多個文件中。該概念於 C# 中首次出現，除了能將一個類別的成員分開存放，還使 ASP.NET 中的代碼後置得以實現。代碼後置實現了 HTML 代碼和後台交互代碼的分離。

file1.cs:

file2.cs:

分部類別這個特性允許將一個類別的編寫工作分配給多個人，一人寫一個文件，便於版本控制。它又可以隔離自動生成的代碼和人工書寫的代碼，例如設計窗體應用程式時。

### 泛型

泛型，或參數化類型，是被 C# 支持的.NET 2.0 特性。不同於 C++ 模版，.NET 參數化類型是在運行時被實例化，而不是編譯時，因此它可以跨語言，而 C++ 模版卻不行。C# 泛型類在編譯時，先生成中間代碼 IL，通用類型符號 T 只是一個占位符；在實例化類時，根據實際數據類型代替 T 並由即時編譯器（JIT）生成本地代碼，其中使用了實際的數據類型，等同於用實際類型寫的普通的類。

它支持的一些特性並不被 C++ 模版直接支持，比如約束泛型參數實現一個接口。另一方面，C# 不支持無類型的泛型參數。不像 Java 中的泛型，在 CLI 虛擬機中，.NET generics 使用具化生成泛型參數，它允許優化和保存類型信息。

泛型類中，可以用 where 關鍵字對參數類型實現約束。例如：

上述表示 T 和 V 必須是 Stack 類或其派生類，T 必須繼承了 IComparable 接口、有無參構造函數、是引用類型；V 必須是值類型。

泛型不僅能作用在類上，也可單獨用在類的方法上，稱為「泛型方法」。

泛型類的靜態成員變量在相同封閉類間共享，不同的封閉類間不共享。

泛型類中的方法重載，參數類型 T 和 V 在運行時確定，不影響這個類通過編譯。C# 的泛型是在實例的方法被調用時檢查重載是否產生混淆，而不是在泛型類本身編譯時檢查。特別地，當一般方法與泛型方法具有相同的簽名時，會覆蓋泛型方法。

### 靜態類別

靜態類別它不能被實例化，並且只能有靜態成員。這同很多過程語言中的模塊概念相類似。

### 迭代器

一種新形式的迭代器它提供了函數式編程中的 generator，使用 `yield return`

類似於 Python 中使用的 `yield`

注意事項：

* foreach 循環時考慮執行緒安全性，不要試圖對被遍歷的集合進行 remove 和 add 等操作
* IEnumerable 接口是 LINQ 特性的核心接口。只有實現了 IEnumerable 接口的集合，才能執行相關的 LINQ 操作，比如 select,where 等

### 匿名方法

匿名方法類似於函數式編程中的閉包。匿名方法是通過使用 delegate 關鍵字創建委託實例來聲明的。例如：

 

### 委託的協變和逆變

委託簽名的協變和逆變，

### 屬性訪問器可以被單獨設置訪問級別

例子：

### 可空類型

可空類型（跟個問號，如 `int? i = null;`）允許設置 `null` 給任何類類型。

### ?? 運算子

（`??`）：如果左運算數表達式的值不為空值時回傳該值，如果為空值則返回右運算數表達式的值。

主要用作將一個可空類型賦值給不可空類型的簡便語法

## C# 3.0 的特性

C# 3.0 發布於 2007 年 10 月 17 日，是.NET Framework 3.5 的一部分，它的新特性靈感來自於函數式編程語言，如：Haskell 和 ML，並廣泛地引入了 Language Integrated Query（LINQ）模式到通用語言運行庫中 e.

### Linq

語言集成查詢（英語：**L**anguage **In**tegrated **Q**uery，縮寫：**LINQ**）： 上下文相關關鍵字 "`from`, `where`, `select`" 可用於查詢 SQL、XML、集合等。這些標識符在 LINQ 上下文中被作為關鍵字，但是它們的增加不會破壞原有的名為 `from`、`where` 或 `select` 的變量。

### 類型初始化器

可寫作：

### 集合初始化器

可寫作

假設 `MyList` 實現了 `System.Collections.IEnumerable` 且有一個 `Add` 方法 method

### 匿名類型

### 局部變量類型推斷

局部變量類型推斷：

等同於

它只是一個語法糖，這個特性被匿名類型聲明時所需要

### Lambda 表達式

Lambda 表達式（無函式名稱的物件方法在程式語言中的表達語法）:

　
: 可寫作

編譯器翻譯 Lambda 表達式為強類型委託或強類型表達式樹。

注意事項：

* 如果只有一個參數，可以省略括號 ()，例如 item=>{Console.WriteLine ("只有一個參數 {0} 的 Lambda 表達式",item); };
* 如果只有一個返回值的語句，可以省略花括號 {}、return 關鍵字、分號，例如 item => {return item % 2 == 0;}; 改寫成：item =>item %2 == 0;
* Lambda 表達式可以分配給 Func，Action 或 Predicate 委託。

### 自動化屬性

編譯器將自動生成私有變量和適當的 getter（get 訪問器）和 setter（set 訪問器），如：

### 擴展方法

擴展方法能夠使現有的類型添加方法，而無需創建新的派生類型、重新編譯或以其它方式修改原始類型。

使用擴展方法，必須在一個非嵌套、非泛型的靜態類中定義一個靜態方法，方法第一個參數必須附加 this 關鍵字作為前綴，第一個參數不能有其它修飾符（如 ref 或者 out），這個方法將被編譯器添加到該 this 的類型中。

注意事項：

* 擴展方法只會增加編譯器的工作，但不會影響程序運行性能（用繼承的方式為一個類型增加特性反而會影響性能）
* 如果原來的類中有一個方法，跟擴展方法一樣，那麼擴展方法不會被調用，編譯器也不會提示

### 分部方法

允許代碼生成器生成方法聲明作為擴展點，如果有人在另一個部分類實現了它才會被包含於原代碼編譯。

1. 分部方法（Partial methods）必須定義在分部類（partial classes）中
2. 定義分部方法需要用 partial 做修飾符
3. 分部方法不一定總是有執行內容的，也就是說定義的方法可以一句操作語句都沒有
4. 分部方法返回值必須是 void
5. 分部方法可以是靜態（static）方法
6. 分部方法可以包含參數，參數可以包含以下修飾詞：this,ref,params
7. 分部方法必須是私有（private）方法

例子：

## C# 4.0 的特性

### dynamic 類型

C# 4.0 新增 dynamic 關鍵字，提供動態編程（dynamic programming），把既有的靜態物件標記為動態物件，類似 javascript, Python 或 Ruby。

dynamic 關鍵字標記的實例被處理成一個特殊包裝的 object 對象，取消了 CLI 的編譯時類型檢查，編譯時被假定支持任何操作，但如果並不實際支持則運行時報錯。

### 具名參數與可選參數

呼叫 OpenFile 時，順序可以完全顛倒：

### 與 COM 組件互動

在 C# 中打開一個 Word 文件：

在 C# 中指定 Excel 的某一格文字：

### 泛型的協變和逆變

C# 4.0 支援協變和逆變，例如在泛型介面可以加上 in、out 修饰字。

## C# 5.0 的特性

### Async

async 和 await 是一對語法糖，允許開發人員非常輕鬆的調用基於 TASK 的異步編程。async-await 關鍵字並不會真的創建一個執行緒池任務，完成這個動作依賴於被調用方法中的函數。

### Caller info attributes

CallerInfoAttributes 用於調試時訪問調用者的信息。包括三個主要的類：

* [CallerMemberName] ：返回調用函數的名稱。
* [CallerFilePath] ：返回調用函數所在源文件全路徑信息 。
* [CallerLineNumber] ：返回調用函數調用具體行號。

### 綁定運算符

　
: =: 綁定運算符簡化了數據綁定

```
comboBox1.Text :=: textBox1.Text; //将文本框的内容绑定到下拉框。  
```

### 帶參數的泛型構造函數

這個的加入給一些設計增加了強大功能，泛型早在 C#2.0 加入後就有著強大的應用，一般稍微設計比較好的框架，都會用到泛型，C#5.0 加入帶參數泛型構造函數，則在原有基礎上對 C# 泛型完善了很多。

### case 支持表達式

以前 case 里只能寫一個具體的常量，而現在可以加表達式

### 擴展屬性

### 支持 null 類型運算

## C# 6.0 的特性

### 只讀自動屬性

不必使用完整屬性語法定義屬性。可以在聲明屬性的位置或類型的構造函數中初始化屬性。

### 自動屬性初始化

```
class MyClass
       {
           public string Name { get; set; } = "Fanguzai";
       }
```

### 具有表達式體的函數成員

可以採用與用於 lambda 表達式相同的輕量語法，聲明代碼為單個表達式的成員。具有立即僅返回表達式結果，或單個語句作為方法主體。C# 6.0 支持方法和屬性 get。C# 7.0 擴展支持了構造函數、終結器、屬性 set、索引器。

### 使用靜態類

可以導入靜態類型的可訪問靜態成員，以便可以在不使用類型名限定訪問的情況下引用成員。

### nameof 運算符

### 內插字符串

以 $ 為前綴構造字符串。 內插字符串表達式類似於包含表達式的模板字符串。內插字符串表達式通過將包含的表達式替換為表達式結果的 ToString 表現形式來創建字符串。

想要在內插字符串中包含大括號「{」 或 「}」，使用兩個大括號，即 「Template:」 或 「」。

### NULL 條件運算符

用於在執行成員訪問 ?. 或索引 ?[ 操作之前，測試是否存在 NULL 值。

### Catch 和 Finally 區塊中的 Await

### 索引初始設定

### 異常過濾器 (Exception filters)

### 集合初始設定式的擴充方法

### 改進的多載解析

## C# 7.0 的特性

### 具有表達式體的函數成員

可以採用與用於 lambda 表達式相同的輕量語法，聲明代碼為單個表達式的成員。具有立即僅返回表達式結果，或單個語句作為方法主體。C# 6.0 支持方法和屬性 get。C# 7.0 擴展支持了構造函數、終結器、屬性 set、索引器。

### out 變數

能夠直接宣告一個變數在它要傳入的地方，當成一個 out 的引數

### 棄元

元組 / 對象的解構：

```
var tuple = (1, 2, 3, 4, 5);
(_, _, _, _, var fifth) = tuple;
```

使用 is/switch 的模式匹配：

```
var obj = CultureInfo.CurrentCulture.DateTimeFormat;
switch (obj)
{
 case IFormatProvider fmt:
   Console.WriteLine($"{fmt} object");
   break;
 case null:
   Console.Write("A null object reference");
   break;
 case object _:
   Console.WriteLine("Some object type without format information");
   break;
}

if (obj is object _)
{
 ...
}
```

對具有 out 參數的方法的調用：

```
var point = new Point(10, 10);
// 只要 x, 不关心 y
point.GetCoordinates(out int x, out _);
```

作用域內獨立使用場景：

```
void Test(Dto dto)
{
   _ = dto ?? throw new ArgumentNullException(nameof(dto));
}
```

### 元組

.NET 有 Tuple 類型，但是它是一個引用類型，會導致性能問題，但是 C＃7.0 帶來了具有值類型的 Tuple，該類型具有更快的性能和可變的類型。

**元組的解構**

大多數時候，不想訪問整個元組包，或者只需要內部值，那麼可以使用元組解構並獲取所需的值

### 記錄類型

記錄類型只不過是屬性和變量的容器。藉助記錄類型可以減少工作量。

### 不可為空的引用類型

### 本地方法

可以在其中使用 out 或 ref。

### 數值字面量中包含下劃線分隔符以提高可讀性

### 模式匹配

允許在 IS 語句和 SWITCH 語句中把模式與任何數據類型匹配。模式可以是常量模式、類型模式、變量模式。

### 變量作為 Ref 從方法返回

通過 Ref 傳遞一個變量，將它作為 Ref 返回，並將它們存儲為 Ref

在上面的示例中，我們通過從方法返回 Ref 找到並替換了字符串。

### 從表達式中拋出異常

## C# 7.1 的特性

### async Main 方法

### default 常值運算式

### 推斷的 tuple 項目名稱

## C# 7.2 的特性

### 有條件的「ref」

### 無需名稱的非尾部命名參數

如果參數的順序正確，則無需為參數配上名稱

### 數值常值中的前置下劃線分隔符

### 私有保護訪問修飾符

只有當前項目里的繼承關係才能用，出了這個程序集就沒辦法用了，即 private protected 為私有繼承（protected 且 internal）。

## C# 7.3 的特性

### 改進 stackalloc 運算符

### 固定語句支持更多類型

固定語句是防止垃圾回收器清除可移動變量的語句，可以使用 fixed 關鍵字創建這些語句。從 C＃7.3 開始，固定語句支持具有以下內容的任何類型：GetPinnableReference () 方法，返回的 ref T 是固定的。

### 非託管約束

一個成員是一個非託管型，如果它是 byte，short，sbyte，int，long，char，bool 類型。可以使用非託管約束來顯示類型參數必須是具有非指針的非託管類型。

還可以使用 System.Enum 和 System.Delegate 作為基類約束。

### 元組支持等於和不等於運算符

### 重載的「in」方法的更改

為「按引用」和「按值」方法命名相同的方法時，它會拋出歧義異常，為避免此錯誤，C＃7.3 使用了「in」方法：

### 擴展參數邊界

可以在構造函數初始值設定項、屬性初始值設定項中定義 out 參數。

### 多個編譯器選項

* 使用公鑰簽名程序集：添加參數作為 - public 簽名。
* 從構建環境替換路徑：用較早的映射源路徑替換構建中的源路徑：-pathmap:path1=sourcePath1,path2=sourcePath2

## C# 8.0 的特性

1. 遞歸的模式匹配
2. 在編譯器可做類型推斷的情況下，允許進一步省略類型聲明

### 可釋放的引用結構

聲明為 ref 的 struct 無權實現任何接口，因為無權實現 IDisposable 接口。必須使我們需要使用可釋放的 ref 結構來訪問 void Dispose () 方法。也可以配置 readonly ref 結構。

### 表達式形式的 Switch 關鍵字

switch 用於模式匹配的表達式及其語法有所變化。

### 空值結合賦值運算符

??= 空值結合賦值運算符（null-coalescing）：

```
some_Value ??= some_Value2;
```

以代替囉嗦的寫法：

```
some_Value = some_Value ?? some_Value2;
```

### 局部靜態函數

將局部函數設為 static 函數，可證明局部函數不包含內部封閉循環中的變量。

### 默認接口方法

允許在聲明接口時為接口成員提供默認實現。

## = 將結構成員設為只讀

現在可以將 struct 成員設為只讀，而不是將整個 struct 成員設置為只讀。

### 插值字符串的增強

string 以 $ 開頭，這樣的 string 則稱為插值 string，可包含插值表達式：

逐字字符串以 @"開頭。在 C＃8 中，$@"" 或者 @$"" 表示可以通過 @或 $ 來開始 string。在早期版本的 C＃中，@僅在 $ 之後被允許。

### stackalloc 作為表達式

語法：stackalloc T [E] 其中 T 是非託管類型，E 是類型 int 的表達式。stackalloc 用作表達式，如果返回結果 System.Span<T>。

### 新型索引 (Index) 和區間（Range）類型

System.Index 和 System.Range 用於從列表 / 數組中獲取項。

^（脫字符）表示索引符號。通常，從數組中獲取項目時，索引以開頭 0，但是當使用這種新類型時，其索引 (indices) 從結尾開始並向上。

.. 範圍運算符獲取其操作數的開始和結束。

在上面的示例中，數組中有四個項；索引從 0 開始，以 3 結束；但是對於索引 (indices) 從 ^ 4（）到 ^ 1。

### using 關鍵字聲明變量

using 關鍵字聲明變量，並在使用該變量的地方結束之前釋放該變量：

### 消費異步流

異步流由一個方法執行，該方法使用 IAsyncEnumerable<T> 枚舉器返回異步流。此方法聲明為 anync 修飾符，此函數還包含 yield return 語句用於返回異步流。為了消費 anync 流，我們需要在實際返回值之前使用 await 關鍵字。

## C# 9.0 的特性

### 新的「Record」類型

記錄類型， 是一種引用類型， 默認是不可變的。 記錄類型的相等判斷可以通過引用或者結構進行判斷的。

* 優點：記錄類型是輕量級的不可變類型，可以減少大量的代碼， 可以按照結構和引用進行比較；
* 缺點：需要實例化大量的對象；

### 「init」訪問子

init 存取子表示該屬性所屬類型僅能在建構函式 (Constructor) 中或是屬性初始化式子中賦予其值，如果嘗試在其他地方設定該屬性的值，在編譯時便會遭編譯器阻止。

範例如下：
在這個範例中，建立了一個 `Student` 類型，並且屬性 `StudentName` 與 `StudentID` 只能在初始化時賦予其值。

如果在此時撰寫以下程式碼：

編譯器便會無法編譯並且擲回錯誤。

而如果要建立學生名稱為「Test Name」，學生 ID 為「0001」的學生，則需要寫成：

或是

### 最上層陳述式或稱頂級語句

在以前的版本，開發者在撰寫最上層陳述式 (如 Program.cs) 程式碼時，需要包含完整的 namespace 與 class 架構，因此如果要撰寫 Hello World 程式時，程式碼就會是：

但是在 C#9.0 之後，最上層語句 (top-level statement) 的程式碼不需要包含 namespace 以及 class，可將其簡化為：

注意， 一個程序中， 只能有一個文件使用頂級語句， 並且頂級語句必須位於命名空間或類型定義之前！

### lambda 棄元參數

```
Func<int, int, int> zero = (_, _) => 0;
Func<int, int, int> func = delegate (int _, int _) { return 0; };
```

在 C# 9 之前，即便不使用的 Lambda 參數也需要給它命名。C# 9 支持棄元參數一方面簡化了命名，另一方面也節省了內存分配。更重要的是它使得編程的意圖更明確，讓人一看就知道這個參數是不用的，增強了代碼的可讀性和可維護性。

### 只能初始化的設置器

Init only setters，只能通過對象初始化進行賦值的屬性。

### 函數指針

使用 delegate\* 可以聲明函數指針。

### 跳過本地初始化

### 原生整數類型

兩個新的整數類型 nint 和 nunit ， 依賴宿主機以及編譯設定。

### 協變返回類型

協變返回類型為重寫方法的返回類型提供了靈活性。覆蓋方法可以返回從被覆蓋的基礎方法的返回類型派生的類型。

```
class Person { public virtual Person GetPerson() { return new Person(); } }
class Student : Person { public override  Student GetPerson() { return new Student(); } }
```

### 模塊初始化代碼

ModuleInitializerAttribute 為組件 (assembly) 定義初始化代碼， 當初始化 / 加載時執行， 可以類比類的靜態構造函數， 但是是組件級別的。

* 必須是靜態的、無參數的、無返回值的方法；
* 不能是范型方法，也不能包含在范型類中；
* 不能是私有函數，必須是公開 (public) 或者內部 (internal) 的函數；

### 靜態 lambda 表達式

static 修飾符添加到 lambda 表達式或匿名方法 。這將無法捕獲局部變量或實例狀態，從而防止意外捕獲其他變量。

### 分部方法擴展

移除了分部方法的下述限制：

* 必須具有 void 返回類型。
* 不能具有 out 參數。
* 不能具有任何可訪問性（隱式 private ）。

### 初始化表達式的簡化

如果創建對象的類型已知時，可以在 new 表達式中省略該類型。

### 在本地函數上添加標記

### GetEnumerator 擴展

可以為任意類型添加一個 GetEnumerator 擴展方法， 返回一個 IEnumerator 或者 IAsyncEnumerator 實例， 從而在 foreach 循環中使用。

### 模式匹配增強

Type patterns 類型匹配，判斷一個變量的類型

Relational patterns 關係匹配：

Conjunctive and patterns 邏輯合取匹配：

Disjunctive or patterns 邏輯析取匹配：

Negated not patterns 邏輯非匹配

Parenthesized patterns 帶括號的優先級匹配：

## C# 10.0 的特性

### record struct

解決了 record 只能給 class 而不能給 struct 用的問題：

```
record struct Point(int X, int Y);
```

### sealed record ToString 方法

可以把 record 里的 ToString 方法標記成 sealed

### 結構的無參數構造函數

無參數的結構體構造函數（Parameterless constructors in structs）。無參構造函數使得 new struct () 和 default (struct) 的語義不一樣。

### 用 with 創建新的匿名類型對象

```
   var x = new { A = 1, B = 2 };
   var y = x with { A = 3 };
```

這裡 y.A 將會是 3 。

### 全局的 using

可以給整個項目啟用 using，不需要每個文件都寫一份。

### 文件範圍的 namespace

以前寫 namespace 還得帶一層大括號。現在如果一個文件里只有一個 namespace 的話，直接在文件開頭寫：
namespace MyNamespace;

### 常量字符串插值

```
   const string x = "hello";
   const string y = $"{x}, world!";
```

### lambda 的改進

#### lambda 可以帶 attributes

```
   f = [Foo] (x) => x; // 给 lambda 设置
   f = [return: Foo] (x) => x; // 给 lambda 返回值设置
   f = ([Foo] x) => x; // 给 lambda 参数设置
```

#### 指定返回值類型

此前 C# 的 lambda 返回值類型靠推導，C# 10 允許在參數列表之前顯式指定 lambda 返回值類型：

```
f = int () => 4;
```

#### 支持 ref 、in 、out 等修飾

```
f = ref int (ref int x) => ref x; // 返回一个参数的引用
```

#### 頭等函數

函數可以隱式轉換到 delegate，於是函數上升為頭等函數 (first function)：

```
   void Foo() { Console.WriteLine("hello"); }
   var x = Foo;
   x(); // hello
```

#### 自然委託類型

lambda 可自動創建自然委託類型，於是不再需要寫出類型：

```
   var f = () => 1; // Func<int>
   var g = string (int x, string y) => $"{y}{x}"; // Func<int, string, string>
   var h = "test".GetHashCode; // Func<int>
```

#### CallerArgumentExpression

使用 CallerArgumentExpression 這個 attribute，編譯器會自動填充調用參數的表達式字符串，例如：

```
   void Foo(int value, [CallerArgumentExpression("value")] string? expression = null)
   {
       Console.WriteLine(expression + " = " + value);
   }
```

當你調用 Foo (4 + 5) 時，會輸出 4 + 5 = 9。這對測試框架極其有用

### tuple 的混合定義和使用

```
   int y = 0;
   (var x, y, var z) = (1, 2, 3);
```

於是 y 就變成 2 了，同時還創建了兩個變量 x 和 z，分別是 1 和 3 。

### 接口支持抽象靜態方法

.NET 6 中這個特性為 preview 特性。

### 泛型 attribute

### 在方法上指定 AsyncMethodBuilder

在方法上用 [AsyncMethodBuilder (...)]，來使用自己實現的 async method builder，代替自帶的 Task 或者 ValueTask 的異步方法構造器。有助於實現零開銷的異步方法。

### line 指示器支持行列和範圍

以前 #line 只能用來指定一個文件中的某一行，現在可以指定行列和範圍：

```
   #line (startLine, startChar) - (endLine, endChar) charOffset "fileName"
    
   // 比如 #line (1, 1) - (2, 2) 3 "test.cs"
```

### 嵌套屬性模式匹配改進

以前在匹配嵌套屬性的時候需要這麼寫：

```
if (a is { X: { Y: { Z: 4 } } }) { ... }
```

現在只需要簡單的：

```
if (a is { X.Y.Z: 4 }) { ... }
```

### 改進的字符串插值

實現接近零開銷的字符串插值。

### Source Generator v2

包括強類型的代碼構建器，以及增量編譯的支持等

## 參見

* .NET Framework
* C Sharp
* Mono

## 參考文獻

1. Archer, Tom. Inside C#. Microsoft Press. 2001. ISBN 0-7356-1288-9.
2. Bart de Smet on Spec# （頁面存檔備份，存於網際網路檔案館）

## 外部連結

* Microsoft .NET Framework （頁面存檔備份，存於網際網路檔案館）
* Mono project （頁面存檔備份，存於網際網路檔案館）