帶你領略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"})
這段程式碼完成了兩件事:
它建立了一個本地變數s,將其賦值為((Professor)person).Subject。
它執行了一次相等性檢查 ((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風格語言的互操作。
本地函數
本地函數是指在另一個函數中所定義的函數。第一眼看來,本地函數似乎只是比匿名函數稍好的一種文法。但它實際上還存在幾個優點:
按照第二條規則推算,你將無法建立一個指向本地函數的委託。這一點對於代碼的組織其實是一個優點,因為你無需建立獨立的函數,並且將現有函數的狀態作為顯式的參數進行傳遞。
部分類的改進
最後示範的特性是一種處理部分類的新方式。在過去,部分類的應用是基於代碼產生優先的概念而出現的。所產生的程式碼將包含一系列部分方法,開發人員可以選擇實現這些方法,以調整類的行為。
通過新的“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#”。