返回列表 上一主題 發帖

[轉貼] C# 7.0 新功能介紹(1/2)

[轉貼] C# 7.0 新功能介紹(1/2)

https://blogs.msdn.microsoft.com/msdntaiwan/2017/04/10/c7-new-features/

這篇文章介紹了 C# 7.0 的新語法。這也是在 2017/03/07 發表的 Visual Studio 2017 中眾多新功能之一。

在 C# 7.0 新增了許多支援的語法,重點擺在改善效能、精簡程式碼、以及資料取用幾個部分。其中最主要的功能之一是 Tuples, 能讓你更容易的一次傳回多筆結果,另外 Pattern Match 新語法則簡化了撰寫針對特定資料型態與條件的程式碼。除此之外,C# 7.0 也包含了其他重要的各種新語法支援。希望所有的這些改變都能讓你更愉快的寫出有效率,簡潔的程式碼,同時也更有生產力。

如果你很好奇我們如何導引出這些功能的設計過程,可以查閱 C# Language design GitHub 網站,在那邊可以找到設計說明文件,設計提案,與大量的討論內容。

如果你覺得這篇文章內容很熟悉,也許是你曾經看過去年八月份 (2016/08) 發表過的版本。在 C# 7.0 最終定案的版本中有少數的異動,這些
異動都來自先前版本的眾多優良的回饋意見。

希望你喜歡 C# 7.0, 盡情享受它, Happy Hacking !!

Mads Torgersen, C# Language PM

譯註:

為了更清楚的表達這篇文章的內容,翻譯時我採用意譯,而非逐句翻譯。我也會適時補充字句,讓文章要表達的意義更清楚完整。

太多專有名詞,翻成中文反而對閱讀沒有幫助,因此這部分我保留原文,但是我會在譯註的部分額外補充說明。
期望這樣能更清楚的讓讀者了解內容。

Microsoft-logo 本篇文章,帶您看到以下 C# 7.0 新功能:
Out 變數 ( out variables )
Pattern Matching (模式匹配 )
使用 pattern 的 is 表達式
使用 patterns 的 switch 陳述式
Tuples
Desconstruction (解構 )
Local functions ( 區域函式 )
改良的 Literal
Ref returns 與 ref locals
非同步的傳回型別
更廣泛的 expression bodies 成員
Throw 運算式


Out 變數 (out variables)
在先前版本的 C# 中,out 參數的使用並不如我們期望的那麼的流暢。呼叫帶有 out 參數的 method 之前,你必須先宣告變數
並且將它當作 out 的參數傳遞才行。通常你不會 (也不需要) 先初始化這變數 (變數的內容會在被呼叫的 method 內覆寫),同時你也不能使用 var 的方式來宣告它, 你必須很明確的指定這變數的完整型別:

public void PrintCoordinates(Point p)
{
    int x, y; // have to "predeclare"
    p.GetCoordinates(out x, out y);
    WriteLine($"({x}, {y})");
}
在 C# 7.0,新增了 out 變數,可以在傳遞 out 參數時同時宣告這個變數:

public void PrintCoordinates(Point p)
{
    p.GetCoordinates(out int x, out int y);
    WriteLine($"({x}, {y})");
}
請留意,這個變數在包含它本身的 { } 封閉區塊範圍內,所以接續宣告後面的程式碼可以直接使用這些變數。
多數類似型態的語法沒有指定可視範圍,該變數可視範圍就等同於宣告他的區塊範圍。

通常 out 變數都會直接被宣告為傳遞的參數,編譯器通常能直接判定參數的型別為何 (除非 method 包含數個互相衝突
的 overloads 而無法判定),因此可以直接使用 var 的方式來宣告它:

p.GetCoordinates(out var x, out var y);
一般來說,我們常常在 Try... 這類的使用模式中用到 out 參數,它會傳回 true 或是 false 來代表執行成功與否,同時藉著 out 參數來傳回成功執行後的結果:

public void PrintStars(string s)
{
    if (int.TryParse(s, out var i)) { WriteLine(new string('*', i)); }
    else { WriteLine("Cloudy - no stars tonight!"); }
}
如果你不在意某個 out 參數的傳回結果,可以使用 _ 代表忽略它:

p.GetCoordinates(out var x, out _); // I only care about x

Pattern Matching (模式匹配)
C# 7.0 開始引入了 patterns (模式) 的概念。抽象的來說,他是可以判定資料是否具備一定 "形狀"(Shape) 的語法元素,並從該數值之中提取需要的資訊。

譯註:
Shape, 代表資料的 "形狀", 精確的來說包含資料本身型別包含哪些成員? 這些成員的數值是否落在預期的範圍?
patterns 可以讓判斷資料 "形狀" 的程式碼更為簡潔明確。

舉例來說,C# 7.0 支援的 patterns 有這幾種:

Constant Patterns (常數模式, 以 c 表示,c 是 C# 的常數表達式), 測試輸入的數值是否與 c 相等。
Type Patterns (類型模式, 以 T x 表示,T 代表型別,而 x 是識別名稱), 測試輸入的數值是否屬於類別 T? 如果是的話就把輸入的數值放到類型為 T 的變數 x 中。
Var Patterns (變數模式, 以 var x 表示, x 是識別名稱), 這種模式下永遠會匹配成功,此時 x 的型別與輸入的數值相同,這模式下只是簡單的把輸入的數值放到 x 之中。
這些只是計畫中的第一步 - pattern (模式) 是 C# 新型態的語法元素,我們期望未來能繼續新增更多的功能。

在 C# 7.0 我們用 pattern 來增強兩種既有的語法結構:

is expression (is 表達式) 現在可以在右方加上 pattern,在之前則只能定義型別。
switch 陳述式中的 case 子句,現在可以比對模式是否符合,過去則只支援常數數值。
在未來的 C# 我們會增加更多適用 pattern 的語法。

使用 pattern 的 is 表達式
來看看使用 constant patterns 與 type patterns 的 is expression 使用範例:

public void PrintStars(object o)
{
    if (o is null) return;     // constant pattern "null"
    if (!(o is int i)) return; // type pattern "int i"
    WriteLine(new string('*', i));
}
如所見,pattern 變數 - 由 pattern 引入的變數,跟前面介紹的 out 變數非常相似,你可以宣告在表達式之中,而且可以直接就近在同可是範圍內直接使用他。

跟 out 變數很相似的地方是,模式變數是可變動的,我們常將 out 變數與 pattern 變數,統稱為 expression 變數。

Patterns 常與 Try... method 一起使用:

if (o is int i || (o is string s && int.TryParse(s, out i)) { /* use i */ }

使用 patterns 的 switch 陳述式
在 C# 7.0,我們也擴大了 switch 陳述式的應用範圍:

switch 陳述式現在可以運用在所有型別 (不再只限於基本類型)
patterns 可以用在 case 子句
case 子句可以附加條件判斷式
這邊有對應的範例程式碼:

switch(shape)
{
    case Circle c:
        WriteLine($"circle with radius {c.Radius}");
        break;
    case Rectangle s when (s.Length == s.Height):
        WriteLine($"{s.Length} x {s.Height} square");
        break;
    case Rectangle r:
        WriteLine($"{r.Length} x {r.Height} rectangle");
        break;
    default:
        WriteLine("<unknown shape>");
        break;
    case null:
        throw new ArgumentNullException(nameof(shape));
}
這裡有幾個 switch 陳述式新增的擴充功能:

case 子句的順序是重要的:
就如同 catch 子句一樣,多個 case 子句之間不再是沒有順序關聯的,而第一個符合條件的 case 子句會被選中。這點非常重要,拿上一個範例程式碼來說,代表正方形的這個 case 子句 (譯註: case Rectangle s when (s.Length == s.Height):) 應該要排在代表長方形的 case 子句 (case Rectangle r:) 前面,結果才會正確。另外,就像 catch 子句一樣,編譯器可以標示出永遠無法執行到的程式碼來協助你。在這之前,你無法也不需要指定多個 case 之間的評估順序,所以這並不是個破壞性的改變 (breaking change)。
default 子句永遠會最後才評估:
即使在上述的例子中,null case 子句被擺在最後,他仍然會在 default 子句之前被檢查。這樣的設計是為了與現有的 switch 陳述句保持相容。然而,好的做法通常會明確的將 default 子句擺在最後面。
擺在最後面的 null case 子句並不會無法被被執行到:
因為 type patterns (類型模式) 依循 is expression 的例子,不會與 null 子句匹配。這可以確保 null 子句不會不小心被任何的 type patterns (類型模式) 給搶走,你必須更清楚該如何處理這種狀況 (或是把它留給 default 子句來處理)。
由 case ... 引進的 pattern 變數 ,他的可視範圍只限於對應的 switch 區段。

Tuples
想要從一個 method 傳回一個以上的傳回值是蠻常見的狀況。但是目前 C# 版本對這需求能提供的作法都不夠好。現有的作法有:

out 參數:
使用上很累贅 (即使在前面的部分已經介紹了改良的語法),而且這方式也無法搭配 async method 一起使用。
使用 System.Tuple<...> 型別來傳回值:
需要手動配置一個 tuple 物件,同時也需要寫些冗長的 code 才能辦到。
替每個 method 都自訂專屬的傳回值型別:
得額外寫大量的 code 來完成這件事,但是目的只是暫時將多個數值組合起來而已。
使用 dynamic 來傳回匿名的型別 (anonymous types):
無法做到靜態型別檢查,同時將會付出很高的效能代價。
為了讓這件事做得更好,C# 7.0 新增了 tuple types 及 tuple literals 的語法:

(string, string, string) LookupName(long id) // tuple return type
{
    ... // retrieve first, middle and last from data storage
    return (first, middle, last); // tuple literal
}
這 method 現在能更有效率的傳回三個字串型別的傳回值了,這範例將三個字串包成一個 tuple。
呼叫這 method 的程式碼將會收到回傳的 tuple 物件,且能透過 tuple 物件個別存取這些封裝在內的資料:

var names = LookupName(id);
WriteLine($"found {names.Item1} {names.Item3}.");
其中 Item1 等等,為 tuple 內的元素預設的名稱,這方法能正常運作, 但是這命名方式終究不能很能清楚表達用途。所以你願意的話可以明確的替它們指定更適合的名稱:

(string first, string middle, string last) LookupName(long id) // tuple elements have names
現在這個 tuple 的元素能用更貼切的名稱來存取之內的元素了:

var names = LookupName(id);
WriteLine($"found {names.first} {names.last}.");
你也可以直接在 tuple literals 內指定元素的名稱:

    return (first: first, middle: middle, last: last); // named tuple elements in a literal
一般來說,你可以互相指派 tuple 的變數,而不用管他的名稱為何: 只要個別的元素都可以被指派,tuple 型別可以自由轉換為其他的 tuple 型別。

Tuples 是 value types, 而且它包含的元素都很單純的被標示為 public, 都是可異動的欄位 (mutable fields)。它們是 "數值相等" (value equality) 的,
意思是只要兩個 tuples 的所有對應的元素都是相等的 (而且 hash code 也必須相同),那這兩個 tuples 就是相等的 (hash code 也會相同) 。

除了傳回多個傳回值的情況之外,在其他地方 tuples 也很有用。例如,如果你需要一個包含多個 Key 的 Dictionary,你只需要拿 tuple 當作 Dictionary 的 Key 就可以了。如果你需要在 List 內的一個元素放置多個不同的數值,只要使用 tuple 型別並且搜尋這個 List。在這些情況中,tuple 都能正常運作。

Tuples 的實作必須依靠底層的泛型結構型別 (generic struct types): ValueTuple<...>。如果你使用的 target framework 版本還未包含它,你只需要透過 NuGet 取得他們即可:

在 "方案總管" 內的 "專案" 上按右鍵,選擇 "管理 NuGet 套件..."
選擇 "瀏覽" 頁籤,同時在 "套件來源" 項目中選擇 "nuget.org"
搜尋 "System.ValueTuple" 並安裝

        靜思自在 : 稻穗結得越飽滿,越會往下垂,一個人越有成就,就要越有謙沖的胸襟。
返回列表 上一主題