這篇文章介紹的是 C# 7 新增的 pattern matching 語法。

C# 7 增加了「模式匹配」(pattern matching)的語法,可用來判斷某個變數或陳述式的「長相」是否符合特定條件,以決定程式要走哪一條執行路徑——這樣說也許太抽象了,待會兒看到範例程式會比較清楚了。

C# 7 在模式匹配這個部分,目前僅支援 is 和 switch 陳述式。從微軟的技術文件來看,未來應該會在這方面繼續強化。接著就來看看這個新語法有何特別之處。

is 陳述式

在 C# 7 之前,如果要判斷某變數是否是某種型別,常會使用 is 運算子:
void Print(object obj)
{
    if (obj == null)    // 若物件為空,便直接返回。
        return;
    if (obj is string)  // 若物件是個字串,就輸出字串長度。
    {
        var s = obj as string;
        Console.WriteLine($"{s.Length}");
    }
}
C# 7 可以這樣寫:
void Print(object obj)
{
    if (obj is null)    // C# 7
        return;
    if (obj is string s) // C# 7
    {
        Console.WriteLine($"{s.Length}");
    }
}
這裡有兩處改寫(註解中標示 C# 7 的地方):
  • 第 3 行:判斷物件是否為 null,現在也可以用 is 了。儘管這算不上什麼大躍進,但的確提供了多一種選擇,讓我們可以一致地使用 is 來判斷物件的「長相」。
  • 第 5 行:這一行就完成了兩件事,即 (1) 判斷 obj 的型別是否為 string;(2) 將物件轉型並指派給新宣告的變數 s
註:如果是判斷「不為 null」的場合,我還是比較喜歡寫成 (obj != null),而不是 (!(obj is null))。當然這只是個人喜好的問題。
值得注意的是,以此方式宣告的區域變數,其有效存取範圍是「在外層包住它的那個區塊」。也就是說,底下的寫法完全沒有問題:
void Print(object obj)
{
    if (!(obj is string s)) 
        return;
    Console.WriteLine(s); // 這裡仍可使用變數 s。
}
此範例的 .NET Fiddle 連結:https://dotnetfiddle.net/ywmqtI

switch 陳述式

上一節的範例程式碼使用了兩個 if 陳述式,而一旦要判斷的條件很多,使用 switch 會比較好。而且,switch 陳述式還可以使用 when 子句來進一步提供其他條件。範例如下:
void Print(object obj)
{
    switch (obj) 
    {
        case null:          // (1)
            return;
        case string s:      // (2)
            Console.WriteLine($"{s.Length}");
            break;
        case DateTime date: // (3)
            Console.WriteLine(date.Year);
            break;
        case int[] numbers when numbers.Length > 0: // (4)
            foreach (int n in numbers)
                Console.WriteLine(n);
            break;
        case Box box when (box.Width > 0 && box.Width == box.Height)
            // (5) 
            break;
        default:
            Console.WriteLine(obj.ToString());
            break;
    }
}
依註解中標示的編號說明:
  1. 跟 is 一樣,在 switch 裡面也可以判斷變數是否為 null
  2. 若 obj 是 string,就轉型成 string 並指派至新宣告的變數 s
  3. 若 obj 是 DateTime,就轉型成 DateTime 並指派至新宣告的變數 date
  4. 這裡使用了 when 子句:只有當 obj 是個整數陣列,而且該陣列的長度大於零的時候,才會執行這個 case 區塊裡面的程式碼。
  5. 只是再多一個 when 子句的例子,並沒有什麼特殊之處。
此範例的 .NET Fiddle 連結:https://dotnetfiddle.net/uRct75
現在讓我們來看一個稍微特別的情況。底下的程式碼在執行時會進入哪一個 case 區塊?
    string str = null;
    switch (str)
    {
        case string s:
            Console.WriteLine("字串");
            break;
        case null:
            Console.WriteLine("null");
            break;
    }
執行結果是輸出字串 “null”,也就是執行了第二個 case 區塊。這是因為,儘管表面上來看,str 的型別是 string,但是骨子裡它什麼東西都不是;它是個 null。在判斷第一個 case 條件時,其作用等同於使用 as 轉型操作:
if ((null as string) != null) // 結果為 false。
{
    Console.WriteLine("字串");
}
由於 null 無法轉型為其他型別,因此 null as string 的轉型仍為 null。於是,這個條件判斷式的結果為 false
另外,底下是個錯誤示範:
switch (obj) 
{
    case string s1:
        break;
    case string s2 when s2.Length > 5: // 編譯失敗!
        break;
}
這是因為第一個 case 的條件涵蓋了第二個 case,亦即第二個 case 永遠不可能執行到。編譯器會幫你挑出這個有問題的寫法。