C#7 特性預覽

infoq發表於2016-04-07

在過去一年間,我們為讀者展示了多個已考慮加入C# 7中的特性。在最近釋出的Visual Studio 15預覽版中,微軟決定為使用者展現這些特性,使其成為C# 7最終釋出的一部分。

元組值型別

.NET提供了一個元組(Tuple)型別,但具體在C#中使用時卻存在著各種各樣的問題。由於元組型別是一個引用型別,因此在一些對於效能相當敏感的程式碼中,你很可能會避免因使用它而造成GC的開銷。同時,元組型別是不可變的,雖然這使跨執行緒共享變得更安全,但也意味著每次進行變更都必須分配一個新的物件。

為了應對這一問題,C# 7將提供一個值型別的元組。這是一個可變型別,對那些重視效能的程式碼來說,這種方式將更為高效。同時,作為值型別,它在每次進行分配時都會生成一個拷貝,因此幾乎沒有產生多執行緒問題的風險。

你可以通過以下語法建立一個元組:

var result = (5, 20);

你也可以選擇對元組中的值進行命名,這一點並不是必須的,只是讓程式碼具有更好的可讀性。

var result = (count: 5, sum: 20);

你可能會想,“很棒的特性,但我自己也能寫得出來”。但下一個特性才是重頭戲。

多返回值

在類C風格的語言中,要在一個函式中返回兩個值始終是一件麻煩事。你只能選擇將結果封裝成某種結構,或是使用輸出引數。與許多函數語言程式設計語言一樣,C#選擇了第一種方式為你提供這一特性:

(int, int) Tally (IEnumerable<int> list)

可以看到,在這裡使用泛用的元組有一個基本問題:我們將無從得知每個欄位的作用。因此,C#選擇通過一個編譯器花招對結果進行命名:

(int Count, int Sum) Tally (IEnumerable<int> list)

我們在此需要強調一點:C#並沒有生成一個新的匿名型別,你所獲得的仍舊是一個元組,但編譯器將假設它的屬性為Count和Sum,而不是Item1和Item2。所以,以下程式碼行的作用都是等價的:

var result = Tally(list);
Console.WriteLine(result.Item1);
Console.WriteLine(result.Count);

請注意一點,我們現在還不具備多賦值語法,如果這種語法最終實現,那麼它的用法可能是這樣的:

(count, sum) = Tally(list);

除了提供簡單的功能性函式之外,多返回值的實用性還體現在非同步程式碼的編寫上,因為在async函式中是不允許使用out引數的。

模式匹配:改進的Switch語法塊

VB與函式式程式設計師對於C#抱怨得最多的一點就是C#中的switch語句功能十分有限。VB開發者希望能夠進行範圍匹配,而習慣了F#或Haskell的開發者則希望能夠使用分解式的模式匹配。C#打算同時提供這兩種特性。

在對型別進行模式匹配時,你可以建立一個變數以儲存轉型的結果。舉例來說,在對一個System.Object使用switch語句時,你可以編寫以下程式碼:

case int x:

如果該物件是數值型別,則變數x將得以賦值。否則的話,程式將按從上至下的順序檢查下一個case語句塊。如果你想更具體地進行匹配,還可以使用範圍檢查:

case int x when x > 0:
case int y:

在這個示例中,如果該物件是正整數,則x程式碼塊將被執行。如果物件是0或負整數,而y程式碼塊將被執行。

如果需要檢查null值,則只需使用以下語法:

case null;

模式匹配:分解

目前為止,我們僅僅展示了某種對VB中已有的特性所做的增量式改進,而模式匹配真正的強大之處在於分解,它可以將某個物件完全拆開,考慮一下以下語法:

if (person is Professor {Subject is var s, FirstName is "Scott"})

這段程式碼完成了兩件事:

  1. 它建立了一個本地變數s,將其賦值為((Professor)person).Subject。
  2. 它執行了一次相等性檢查 ((Professor)person).FirstName == "Scott"。

如果將其用C# 6程式碼改寫則是這樣:

var temp = person as Professor;
if (temp != null && temp.FirstName == "Scott")
{
    var s = temp.Subject

在最終釋出中,我們預計能夠同時看到對switch語句塊的這兩種改進。

引用返回

對於大資料結構進行引用傳遞比起值傳遞要快得多,因為後者需要對整個結構進行拷貝。與之類似,返回一個大資料結構的引用一樣能夠提升速度。

在類似於C這樣的語言中,可以通過指標返回某個結構的引用。這種方式會帶來一個常見的問題,即指標所指向的記憶體可能會因為某種原因而已經被回收了。

C#通過使用引用的方式迴避這一問題,引用本身是一個附加了規則的指標。最重要的一條規則是,你不能夠返回某個本地變數的引用。如果你嘗試這樣做,那麼該變數所引用的棧資訊在函式返回時就已經變得不可訪問了。

在微軟的展示程式碼中,它所返回的引用指向一個陣列中的某個結構。由於它實質上是指向陣列中某個元素的指標,因此隨後可以對陣列本身進行修改。舉例來說:

var x = ref FirstElement(myArray)
x = 5; //MyArray[0] now equals 5

這一語法的用例是對效能高度敏感的程式碼,在大多數應用中都無需使用這一特性。

二進位制字面值(Binary Literals)

此次釋出還引入了一個小特性,即二進位制字面值。這一語法只是一個簡單的字首而已,例如5可以表示為“0b0101”。這一特性的主要用例是設定基於flag的列舉,以及建立位掩碼(bitmask),以用於與C風格語言的互操作。

本地函式

本地函式是指在另一個函式中所定義的函式。第一眼看來,本地函式似乎只是比匿名函式稍好的一種語法。但它實際上還存在幾個優點:

  • 首先,你無需為其分配一個委託以儲存該函式。這不僅減少了記憶體壓力,同時還允許編譯器對該函式進行內聯操作。
  • 其次,在建立閉包時,也無需為其分配一個物件,因為它能夠直接訪問本地變數。這一點同樣能夠改善效能,因為它也減少了GC的壓力。

按照第二條規則推算,你將無法建立一個指向本地函式的委託。這一點對於程式碼的組織其實是一個優點,因為你無需建立獨立的函式,並且將現有函式的狀態作為顯式的引數進行傳遞。

部分類的改進

最後演示的特性是一種處理部分類的新方式。在過去,部分類的應用是基於程式碼生成優先的概念而出現的。所生成的程式碼將包含一系列部分方法,開發者可以選擇實現這些方法,以調整類的行為。

通過新的“replace”語法,開發者就多了一種新選擇,能夠以最直接的方式編寫程式碼,隨後再引入程式碼生成器,並重寫這些方法。以下將通過一個簡單的示例表現開發者的程式碼編寫方式:

public string FirstName {get; set;}

簡單又清晰,但完全不符合XAML風格應用的寫法。因此,程式碼生成器將生成如下程式碼:

private string m_FirstName;
static readonly PropertyChangedEventArgs s_FirstName_EventArgs =new PropertyChangedEventArgs("FirstName")

replace public string FirstName {
    get {
        return m_FirstName;
    }
    set {
        if (m_FirstName == value)
            return;
    m_FirstName = value;
    PropertyChanged?.Invoke(this, m_FirstName_EventArg);
}

通過“replace”關鍵字,所生成的程式碼將直接替換手寫的程式碼,新增所缺失的功能。在這個示例中,我們甚至還能夠處理一些開發者經常會忽略的麻煩的部分,例如對EventArgs物件進行快取。

雖然這個官方示例僅用於屬性變更通知,但這一技術還可用於各種“面向切面程式設計(AOP)”的場景,例如在程式碼中注入日誌記錄、安全檢查、引數校驗以及其他各種繁瑣的樣板式程式碼。

如果讀者想實際瞭解一下這些特性,可以觀賞Channel 9中的視訊“The Future of C#”。

英文原文:C# 7 Features Previewed

相關文章