# 必備 .NET - 設計 C# 7
作者?[Mark Michaelis](https://msdn.microsoft.com/zh-cn/magazine/mt149362?author=Mark+Michaelis)?| 2015 年 12 月

當您閱讀本文時,C# 7 設計團隊已討論、規劃、實驗和計劃了大約一年之久。在本期中,我將介紹他們一直在探討的一些觀點示例。
在查看時,請注意目前這些仍是要在 C# 7 中體現的觀點。雖然一些觀點只是經過了團隊討論階段,但另一些觀點已進入了實驗實現階段。無論如何,所有這些概念均尚未最終敲定;很多觀點可能會夭折;甚至是那些已經進入后期階段的觀點也可能會在最終確定語言的最后幾個階段被推翻。
## 聲明可以為 null 和不可以為 null 的引用類型
C# 7 討論中涌現的一些最重要的觀點也許與進一步改進 null 的處理方式有關,類似于 C# 6.0 的 null 條件運算符。其中最簡單的一項改進可能是,在執行編譯器或分析器驗證(訪問可以為 null 的類型實例)之前,先檢查類型實際上是否是不可以為 null 的類型。
如果需要不可以為 null 的引用類型,且您能夠完全避免 null,會怎樣? 此觀點旨在聲明引用類型是會允許 null (string?),還是會避免 null (string!)。從理論上講,甚至可以假定新代碼中的所有引用類型聲明默認情況下都不可以為 null。然而,正如與我合著“必備 C# 6.0”一書的作者 Eric Lippert 所指出的,確保在編譯時引用類型永遠不為 null 極為困難 ([bit.ly/1Rd5ekS](http://bit.ly/1Rd5ekS))。即便如此,還是可以確定類型可能為 null且尚未取消引用的的情況,而無需檢查是否是不可以為 null 的類型。或者,也可能發生以下情況:類型可能被分配為 null,盡管聲明意圖是分配不可以為 null 的類型。
為了擴大受益范圍,設計團隊正在討論能否對參數使用不可以為 null 的類型聲明,以便自動生成 null 檢查(盡管這可能會成為一項可選決定,以避免任何非預期的性能降低,除非可以在編譯時這樣做)。
(諷刺的是,C# 2.0 添加了可以為 null 的值類型,因為在很多情況下(如從數據庫中檢索的數據),有必要讓整數包含 null 值。現在,在 C# 7 中,設計團隊正在考慮支持相反的引用類型。)
對于不可以為 null 的類型(例如,string! 文本)的引用類型支持,另一個有趣考慮是公共中間語言 (CIL) 中的實現情況。兩個最常用的方案是將它映射到 NonNullable 類型語法,或者像在 [Nullable] 字符串文本中一樣利用屬性。后者是目前的首選方法。
## 元組
元組是設計團隊考慮為 C# 7 添加的另一項功能。此主題已在早期語言版本中多次被提出,但仍未予以落實。根據此觀點,可以在集合中聲明類型,這樣聲明中就能包含多個值;同樣地,方法也可以返回多個值。若要理解此概念,請查看下面的示例代碼:
~~~
public class Person
{
? public readonly (string firstName, int lastName) Names; // a tuple
? public Person((string FirstName, string LastName)) names, int Age)
? {
??? Names = names;
? }
}
~~~
如列表所示,借助元組支持,您可以將類型聲明為元組,其中包含兩個或多個值。可以在使用數據類型的所有情景(包括字段、參數、變量聲明或方法返回)中利用此功能。例如,下面的代碼片段會從方法中返回元組:
~~~
public (string FirstName, string LastName) GetNames(string! fullName)
{
? string[] names = fullName.Split(" ", 2);
? return (names[0], names[1]);
}
public void Main()
{
? // ...
? (string first, string last) = GetNames("Inigo Montoya");
? // ...
}
~~~
在此列表中,有返回元組的方法,以及 GetNames 結果被分配到的第一個和最后一個變量聲明。請注意,此分配是基于元組內的順序(而不是接收變量的名稱)。想想我們目前使用的一些替代方法(如數組、集合、自定義類型或輸出參數),元組確實具有吸引力。
可以將許多選項與元組結合使用。下面介紹了一些審議選項:
* 元組可以有命名或未命名的屬性,如下所示:
~~~
var name = ("Inigo", "Montoya")
~~~
和:
~~~
var name = (first: "John", last: "Doe")
~~~
* 結果可以是匿名類型或顯式變量,如下所示:
~~~
var name = (first: "John", last: "Doe")
~~~
或:
~~~
(string first, string last) = GetNames("Inigo Montoya")
~~~
* 您可以將數組轉換成元組,如下所示:
~~~
var names = new[]{ "Inigo", "Montoya" }
~~~
* 您可以按名稱訪問各個元組項,如下所示:
~~~
Console.WriteLine($”My name is { names.first } { names.last }.”);
~~~
* 可以推斷未明確指定的數據類型(大體上遵循匿名類型使用的相同方法)
盡管元組還有很多復雜之處,但在大多數情況下,元組遵循的是語言內架構完善的結構,所以它們可以強有力地支持 C# 7 中的功能。
## 模式匹配
模式匹配也是 C# 7 設計團隊經常討論的主題。或許,關于模式匹配的一種更易理解的呈現是,在 case 語句中支持表達式模式(而不僅僅是常量)的擴展 switch(和 if)語句。(若要與擴展 case 語句對應,switch 表達式類型不能局限于擁有對應的常量值的類型)。借助模式匹配,您可以查詢模式的 switch 表達式。例如,您能夠查詢 switch 表達式是特定的類型、具有特定成員的類型,還是匹配特定“模式”或表達式的類型。例如,假設 obj 可能是 Point 類型,并且其 x 值大于 2:
~~~
object obj;
// ...
switch(obj) {
? case 42:
??? // ...
? case Color.Red:
??? // ...
? case string s:
??? // ...
? case Point(int x, 42) where (Y > 42):
??? // ...
? case Point(490, 42): // fine
??? // ...
? default:
??? // ...
}
~~~
有趣的是,當給定的表達式作為 case 語句時,也有必要允許表達式作為 goto case 語句上的自變量。
為了支持 Point 類型的 case 語句,Point 上必須有一些處理模式匹配的成員類型。在此示例中,需要可提取兩個 int 類型的自變量的成員。例如,成員:
~~~
public static bool operator is (Point self out int x, out int y) {...}
~~~
請注意,如果沒有 where 表達式,case Point(490, 42) 可能永遠無法達到,進而會導致編譯器發出錯誤或警告。
switch 語句的限制因素之一是,它不返回值,而是執行代碼塊。模式匹配的新增功能可以支持返回值的 switchexpression,如下所示:
~~~
string text = match (e) { pattern => expression; ... ; default => expression }
~~~
同樣,is 運算符可支持模式匹配,不僅允許進行類型檢查,還支持就類型上是否存在特定成員進行更通用的查詢。
## 記錄
作為 C# 6.0 中考慮添加的簡化“構造函數”聲明語法(但最終遭到拒絕)的延續,支持在類定義中嵌入構造函數聲明,我們將這種概念稱為“記錄”。 例如,假設聲明如下:
~~~
class Person(string Name, int Age);
~~~
此簡單語句會自動生成以下內容:
* 構造函數:
~~~
public Person(string Name, int Age)
{
? this.Name = Name;
? this.Age = Age;
}
~~~
* 只讀屬性,從而創建不可變類型
* 等同性實現(如 GetHashCode、等于、運算符 ==、運算符 != 等)
* ToString 的默認實現
* “is”運算符的模式匹配支持
盡管會生成大量代碼(考慮到僅僅一個很短的代碼行就創建了它的全部),但我們希望可以為手動編碼(本質上是樣本實現)提供相應的重要快捷方式。此外,所有代碼都可以被視為顯式實現中的“默認”代碼,其中的任意內容將具有優先權,并阻止生成相同的成員。
與記錄有關的一個更棘手的問題是,如何處理序列化。相當典型的做法大概是將記錄用作數據傳輸對象 (DTO),但仍不明確如何(若有措施)支持此類記錄的序列化。
與記錄相關的是,支持 with 表達式。借助 with 表達式,您可以根據現有對象對新對象進行實例化。以 person 對象聲明為例,您可以通過以下 with 表達式新建一個實例:
~~~
Person inigo = new Person("Inigo Montoya", 42);
Person humperdink = inigo with { Name = "Prince Humperdink" };
~~~
生成的與 with 表達式對應的代碼如下所示:
~~~
Person humperdink = new Person(Name: "Prince Humperdink", Age: inigo.42 );
~~~
不過,另一建議是與其依賴 with 表達式的構造函數簽名,更可取的做法是將它轉換成 with 方法的調用,如下所示:
~~~
Person humperdink = inigo.With(Name: "Prince Humperdink", Age: inigo.42);
~~~
## 異步流
為了加強 C# 7 中的異步支持,處理異步序列的概念非常新奇。以 IAsyncEnumerable 為例,它的屬性為 Current 且方法為 Task MoveNextAsync。您可以使用 foreach 循環訪問 IAsyncEnumerable 實例,并讓編譯器負責異步調用流中的每個成員,即執行 await 以確定序列(可能是信道)中是否有要處理的另一元素。對此,還有很多需要評估的注意事項;其中最不需要注意的是,所有返回 IAsyncEnumerable 的 LINQ 標準查詢運算符可能會出現的 LINQ 膨脹。此外,如何公開 CancellationToken 支持和 Task.ConfigureAwait 仍不確定。
## 命令行上的 C#
我熱衷于研究 Windows PowerShell 如何讓 Microsoft .NET Framework 可用于命令行接口 (CLI),我特別感興趣的一個方面(也許是我最喜歡的一項審議功能)是支持在命令行上使用 C#;通常將這個概念稱為支持讀取、求值、打印、循環 (REPL)。正如人們所希望的一樣,REPL 支持會隨附 C# 腳本功能,這在不繁瑣的簡單方案中不需要使用所有常見形式(如類聲明)。沒有編譯步驟,REPL 會需要新指令來引用程序集和 NuGet 包,以及導入其他文件。目前正在討論中的方案會支持:
* 用于引用其他程序集或 NuGet 包的 #r。變體是 #r!,它甚至允許訪問內部成員,盡管有一些約束。(這適用于您有要訪問的程序集的源代碼的情況。)
* 用于添加整個目錄的 #l(與 F# 類似)。
* 用于導入其他 C# 腳本文件的 #load,方法與您在項目中添加腳本文件幾乎相同,不同之處在于現在順序很重要。(請注意,可能不支持導入 .cs 文件,因為不允許在 C# 腳本中使用命名空間。)
* 在執行的同時開啟性能診斷的 #time。
您可以期待即將與 Visual Studio 2015 Update 1 一同發布的首版 C# REPL(以及支持相同功能集的已更新交互式窗口)。有關更多信息,請訪問?[Itl.tc/CSREPL](http://itl.tc/CSREPL),以及查看我下個月的專欄。
## 總結
雖然有準備了一年的材料,但若要探究設計團隊的所有工作,我們還有其他太多信息需要了解。即使是我介紹的那些觀點,您也還是需要考慮其他許多詳細信息(注意事項和優勢)。不過,我希望您現在已經了解設計團隊一直在探討的觀點,以及他們正如何尋求改進已經非常出色的 C# 語言。如果您想直接查閱 C# 7 設計說明,并提供您自己的反饋意見,則可以跳轉到?[bit.ly/CSharp7DesignNotes](http://bit.ly/CSharp7DesignNotes)?進行討論。
* * *
Mark Michaelis?*是 IntelliTect 的創始人,擔任首席技術架構師和培訓師。在近二十年的時間里,他一直是 Microsoft MVP,并且自 2007 年以來一直擔任 Microsoft 區域總監。Michaelis 還是多個 Microsoft 軟件設計評審團隊(包括 C#、Microsoft Azure、SharePoint 和 Visual Studio ALM)的成員。他在開發者會議上發表了演講,并撰寫了大量書籍,包括最新的“必備 C# 6.0(第 5 版)”([itl.tc/-EssentialCSharp](http://itl.tc/%c3%82%c2%adEssentialCSharp))。可通過他的 Facebook?[facebook.com/Mark.Michaelis](http://facebook.com/Mark.Michaelis)、博客[IntelliTect.com/Mark](http://intellitect.com/Mark)、Twitter @markmichaelis 或電子郵件?[mark@IntelliTect.com](mailto:mark@IntelliTect.com)?與他取得聯系。*
- 介紹
- Visual Studio - 用于 Web 開發的新式工具: Grunt 和 Gulp
- 新員工 - 放長錢釣大魚
- Microsoft Azure - Azure Service Fabric 和微服務體系結構
- 數據點 - Aurelia 與 DocumentDB 結合: 結合之旅(第 2 部分)
- 游戲開發 - Babylon.js: 構建 Web 基本游戲
- 測試運行 - 面向 .NET 開發者的 Spark 簡介
- Xamarin - 使用 Xamarin.Forms 構建跨平臺用戶體驗
- 孜孜不倦的程序員 - 如何成為 MEAN: 快速輸入
- Microsoft Azure - Azure、Web API 和 Redis 如何有助于加快數據交付
- 必備 .NET - 設計 C# 7
- 新式應用 - 需要了解的 Windows 10 應用開發概念
- 別讓我打開話匣子 - 重構高等教育
- 編者注 - 再見 Kenny