返回列表 上一主題 發帖

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

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

https://blogs.msdn.microsoft.com/msdntaiwan/2017/04/10/c7-new-features/
這篇文章介紹了 C# 7.0 的新語法。這也是在 2017/03/07 發表的 Visual Studio 2017 中眾多新功能之一。

接續上一篇:
http://forum.twbts.com/viewthread.php?tid=20801&extra=

Desconstruction (解構 )
另一個使用 tuples 的方式是將他們 deconstruct (解構)。Deconstructing declaration (解構宣告) 是用來將 tuple (或是其他值) 裡面的部分拆解並個別指派到其他新的變數用的語法:

(string first, string middle, string last) = LookupName(id1); // deconstructing declaration
WriteLine($"found {first} {last}.");
在 deconstructing declaration (解構宣告) 中,可以在個別的變數上使用 var:

(var first, var middle, var last) = LookupName(id1); // var inside
甚至你可以在括號外面只用單一一個 var:

var (first, middle, last) = LookupName(id1); // var outside
你也可以透過 deconstructing assignment (解構指派) 將 tuple 解構後指派到一個既有的變數:

(first, middle, last) = LookupName(id2); // deconstructing assignment
Deconstruction 不只適用於 tuple,任何型別只要它包含 deconstructor (解構式, 無論是定義
在 instance method 或是 extension method 都可以) ,就可以被解構:

public void Deconstruct(out T1 x1, ..., out Tn xn) { ... }
在這個 deconstructor 裡定義的所有 out 參數,就是該型別物件解構後的所有項目。
(為何在這邊我們使用 out 參數,而不直接傳回 tuple ? 因為這樣就可以讓你為不同數量的
變數,分別定義多個 overloads (多載))

class Point
{
    public int X { get; }
    public int Y { get; }

    public Point(int x, int y) { X = x; Y = y; }
    public void Deconstruct(out int x, out int y) { x = X; y = Y; }
}

(var myX, var myY) = GetPoint(); // calls Deconstruct(out myX, out myY);
你可以用這樣常見的模式,讓 constructor 與 deconstructor 的參數對稱排列。
就如同 out 變數的語法,我們允許你在 deconstructor 中 "忽略" 你不在意的 out 參數:

(var myX, _) = GetPoint(); // I only care about myX
譯註: 請勿將這裡介紹的 deconstructor 與一般物件導向語言 (如: C++, C# 都有) 常見的 descructor 搞混了。
這個段落介紹的 C# 解構式 (deconstructor), 是定義物件如何 "拆解" 為多個獨立的變數。拆解後原物件仍然存在。
而 C# 與 constructor (建構式) 作用相反的 descructor (解構函式), 則是定義物件要被銷毀前必須執行的動作。
兩者的中文譯名都同樣是 "解構" 請特別留意。
對於 C# descructor 的說明,可以參考: https://msdn.microsoft.com/en-us/library/66x5fx1b.aspx

Local functions (區域函式)
有時,輔助函式只有在使用他的函式內才有意義。現在這種情況下,你可以在其他函式內宣告 local functions (區域函式):

public int Fibonacci(int x)
{
    if (x < 0) throw new ArgumentException("Less negativity please!", nameof(x));
    return Fib(x).current;

    (int current, int previous) Fib(int i)
    {
        if (i == 0) return (1, 0);
        var (p, pp) = Fib(i - 1);
        return (p + pp, p);
    }
}
在 local function (區域函式) 內,可以直接使用封閉區塊內的 parameters (參數) 與 local variables (區域變數),用法及規則就跟 lambda 運算式 的用法一樣。

舉例來說,iterator method 通常外面都需要包覆另一個 non-iterator method ,用來在呼叫時做參數檢查 (iteraotr 在這時並不會執行,而是在 MoveNext() 被呼叫時才會啟動)。這時 local function 就非常適合在這裡使用:

public IEnumerable<T> Filter<T>(IEnumerable<T> source, Func<T, bool> filter)
{
    if (source == null) throw new ArgumentNullException(nameof(source));
    if (filter == null) throw new ArgumentNullException(nameof(filter));

    return Iterator();

    IEnumerable<T> Iterator()
    {
        foreach (var element in source)
        {
            if (filter(element)) { yield return element; }
        }
    }
}
同樣的例子,不用 local function 的話,就必須把該 method 定義在 Filter 後面,將 iterator 宣告為 private method。這樣會導致封裝性被破壞: 其他成員可能意外的使用它 (而且參數檢查會被略過)。同時,所有原本 local function 需要取用的區域變數與參數,都必須做一樣的處理 (變成 private members)。

改良的 Literal
C# 7.0 允許在 number literal (數字常數) 中,用 _ 當作 digit separator (數字分隔器):

var d = 123_456;
var x = 0xAB_CD_EF;
你可以將 _ 放在數字中的任何位置,來提高程式碼的可讀性,完全不會對數值本身有任何影響。

此外,C# 7.0 也引入二進位的常數表示方式,你現在可以直接用二進位的方式來取代過去十六進位 (例: 0x001234) 的表示方式。例如:

var b = 0b1010_1011_1100_1101_1110_1111;

Ref returns 與 ref locals
如同你可以在 C# 用參考的方式傳遞參數 (使用 ref 修飾詞),你現在也可以用同樣的方式將區域變數的數值用參考的方式傳回。

public ref int Find(int number, int[] numbers)
{
    for (int i = 0; i < numbers.Length; i++)
    {
        if (numbers == number)
        {
            return ref numbers; // return the storage location, not the value
        }
    }
    throw new IndexOutOfRangeException($"{nameof(number)} not found");
}

int[] array = { 1, 15, -39, 0, 7, 14, -12 };
ref int place = ref Find(7, array); // aliases 7's place in the array
place = 9; // replaces 7 with 9 in the array
WriteLine(array[4]); // prints 9
這在回傳大型資料結構時相當有用。舉例來說,遊戲程式可能會預先配置龐大的陣列來存放結構資料 (這樣是為了避免執行過程中發生 garbage collect,
導致遊戲暫停)。現在 method 可以直接用參考的方式傳回結構的資料,呼叫端可以直接讀取與修改它的內容。

同時,有些搭配限制來確保這樣做是安全的:

你只能傳回 "能夠安全傳回" 的參考: 一個是外界傳遞給你的參考,另一個是指向目前物件的 fields (欄位) 的參考。
ref locals 在初始化時會被指向某個儲存位置,一旦指派之後無法再更改。

非同步的傳回型別
到目前為止,C# 的非同步 method 限定必須傳回 void, Task 或是 Task<T> 這幾種型別。C# 7.0 開始,也允許你用同樣的方式,從非同步方法傳回你定義的其他型別。

舉例來說,我們現在可以定義 ValueTask<T> 這個 struct 型別當作傳回值。
這可以避免當非同步執行的結果已經可用,但是卻因為要進行 Task<T> 的配置,而導致非同步執行的結果還在等待中 (awaiting 狀態)。許多涉及 buffering(緩衝) 的非同步操作時,這做法可以明顯地降低配置的次數,同時能帶來明顯的效能提升。

譯註: 例如非同步 I/O 的操作,我們會用非同步的方式將檔案的內容讀到 buffer 內,完成後再不斷重複同樣動作,直到檔案讀取完畢為止,這個動作也許會被重複上千萬次。此時由 Task<T> 替換為 ValueTask<T> 可能可以帶來明顯的效能提升。

也有很多其他的情況下,你可以想像自訂 "task-like" 的應用類型會很有用。要正確地建立它們並不是那麼的直觀,所以我們也不期待大部分的人能正確的使用它們。但是它們可能開始會出現在其他的框架或是 API,而呼叫者可以像過去使用 Task 一樣的使用他,傳回值與 await 等待結果。

更廣泛的 expression bodies 成員
在 C# 6.0 以前,expression bodied methods, properties(屬性) 等功能大受歡迎,但不是所有的成員都可以
使用。在 C# 7.0 中,accessors (存取子), constructor (建構式) 與 finalizers (終結器) 都已加到可以使用 expression bodies 的清單中:

class Person
{
    private static ConcurrentDictionary<int, string> names = new ConcurrentDictionary<int, string>();
    private int id = GetId();

    public Person(string name) => names.TryAdd(id, name); // constructors
    ~Person() => names.TryRemove(id, out *);              // destructors
    public string Name
    {
        get => names[id];                                 // getters
        set => names[id] = value;                         // setters
    }
}
這個新語法的範例程式並非來自 Microsoft C# 編譯器的團隊,而是由社群成員貢獻的。太棒了! Open source!

Throw 運算式
要在運算式之中丟出一個例外 (exception) 是很容易的,只要呼叫 method (在 method 內擲出 exception) 就可以了。但是在 C# 7.0 我們允許在運算式之中直接就地丟出 exception:

class Person
{
    public string Name { get; }
    public Person(string name) => Name = name ?? throw new ArgumentNullException(nameof(name));
    public string GetFirstName()
    {
        var parts = Name.Split(" ");
        return (parts.Length > 0) ? parts[0] : throw new InvalidOperationException("No name!");
    }
    public string GetLastName() => throw new NotImplementedException();
}

&#127775; 更多最新文章及資訊 >> MSDN 台灣粉絲專頁 & MSDN 台灣部落格

        靜思自在 : 【時日莫空過】一個人在世間做了多少事,就等於壽命有多長。因此必須與時間競爭,切莫使時日空過。
返回列表 上一主題