C# 11 新功能 -- List Patterns

模式比對 (patterns matching) 也是近幾版 C# 的發展重點方向之一,在 C# 11 的瘋狂程度比較趨緩,帶來一個比較容易理解的概念 List Patterns

List Patterns 用來解決清單(像是 陣列或List<T> 這一類的東西) 比對的問題,先來一個最基本的例子,有一個 List<int> 型別變數名稱為 list,欲比對其內容是否為 {1,2,3,5,7,11},過去可能會採用迴圈或linq語法,現在可以這麼做:

 List<int> list = new List<int> { 1, 2, 3, 5, 6, 7 };
 var result = list is [1, 2, 3, 5, 7, 11];
搭配 discard pattern

換個題目,現在要比對的是 index 1 的位置是否為 2:

List<int> list = new List<int> { 1, 2, 3, 5, 6, 7 };
var result = list is [_,2,_,_,_,_];

這種比對就是利用 discard 忽略其他的元素,可是單純用 discard 有個麻煩,就是元素數量必須相同。解決這麻煩就是搭配 range pattern。

搭配 range pattern

承上面的題目,可以改寫為:

List<int> list = new List<int> { 1, 2, 3, 5, 6, 7 };
var result = list is [_, 2, ..];

不管後面有多少元素,反正只要 index 1 的元素是 2 就沒問題。

先有了以上的觀念後,後面來配合 switch 使用:

int[] array = { 1,9,8,7,6,5,3};
var result = array switch
{
    [1, .. var s, 3] => string.Join("-", s),
    [1, 2] => "A",
    [2, 5] => "B",
    [1, _] => "C",
    [..] => "D",
    null => "E"
};

解釋一下比對流程 

(1) 如果 array 開頭是 1, 結尾是 3,不管這中間有多少其他的元素,甚至於沒有元素 (若 array = {1, 3} 就是中間沒有素) 都算匹配,並且搭配 var pattern,把中間的元素轉換成 int[] 並指派給 s 變數,所以照上面的程式碼這個 s 變數所指向的 int[] 內容就是 {9,8,7,6,5};那如果是 array = {1,3} ,則 s 會指向一個長度為零的 int[]。
(2) 如果 array = {1,2} 則匹配成功。
(3) 如果 array = {2,5} 則匹配成功。
(4) 如果 array 的第一個元素是1,整個 array 只有兩個元素,則匹配成功。
(5) 只要陣列的執行個體是存在的,就匹配成功。
(6) 如果 s 變數是 null 則匹配成功。

List Pattern 搭配上遞迴會產生一些很有趣的應用,比如說有一個整數陣列,要取得每兩個一組的和,若只剩下一個元素,則取該元素為結果。以上方程式碼的 { 1, 9, 8, 7, 6, 5, 3 } 為例,結果則為 {10, 15, 11, 3 },可以採用以下程式碼來計算:

static void Main(string[] args)
{           
    int[] array = { 1, 9, 8, 7, 6, 5, 3 };
    List<int> list = new List<int>();
    recursive(array, list);
    Console.Write(string.Join(",", list));         

    void recursive(int[] source, List<int> r)
    {
        switch (source)
        {
            case [var x, var y, .. var z]:
                r.Add(x + y);
                recursive(z, r);
                break;
            case [var x]:
                r.Add(x);
                break;
        };
    }
}

搭配了區域函式,剛開始看一定很不習慣,但這手法挺有意思的。範例可以參考我的 github