模式比對概觀

Tip

本文屬於 基礎部分, 適合已經至少懂一種程式語言並正在學習 C# 的開發者。 如果你是程式新手,建議先從 入門 教學開始。 有經驗的 C# 開發者可以使用 pattern 參考 來掌握完整的語法和規則。

來自另一種語言? C# 模式的角色類似於 Java 的模式匹配和 Python match 的案例。 C# 在 switch 運算式、switch 陳述式及 is 運算式中使用模式。

模式匹配 是將模式套用到表達式上。 模式是一種用來測試輸入表達式的條件。 模式輸入就是那個表達式。 C# 評估輸入表達式;結果即為 評估值。 該模式測試該值是否有特定類型、是否等於特定值或具有特定形狀。 當模式匹配時,程式碼可以使用測試中學習到的資訊,例如更具體的類型或從物件中擷取的值。

你可以在三種情境中使用圖案:

  • 在 is 運算子的右側。
  • 在 switch 陳述式的 case 標籤中。
  • 在 switch 運算式的一個分支中。

模式會測試已評估的輸入是否符合型別、常數,或可包含巢狀模式的結構。 布林條件也可以比較兩個獨立計算且值非常數的表達式。 當任一表格都能表達相同的測試時,選擇較易閱讀的形式。

例如,以下方法使用一個 switch 表達式來選擇傳遞訊息:

static string GetDeliveryMessage(Delivery? delivery) =>
    delivery switch
    {
        null => "No delivery was scheduled.",
        ExpressDelivery express => $"Express package {express.TrackingCode}",
        StandardDelivery { Days: <= 2 } => "Standard delivery arriving soon",
        _ => "Standard delivery"
    };

屬性模式可以包含外型測試和巢狀模式,但並非每個屬性模式都必須具備。 在 StandardDelivery { Days: <= 2 }中,外層模式接收 delivery 表達式並測試其型別。 Days屬性表達式隨後成為巢狀關係模式<= 2的輸入。

前述 switch 的表達式是輸入表達式。 支撐桿內的每條線都是 開關臂。 模式出現在 =>之前,結果則出現在它之後。 C# 評估輸入表達式,然後依文字順序選擇第一條臂,其模式相符且可選 when 守衛為 true。 可選 when 的守衛是模式後寫的額外布林條件。 前述範例顯示以下模式:

  • null 是一個 持續的模式。 它會測試 delivery 運算式的求值結果是否為 null。
  • ExpressDelivery express 是一種由兩部分組成的 宣告模式 。 ExpressDelivery 是類型測試部分。 它會測試評估值是否為非空物件,其執行時型別與 ExpressDelivery相容。 express 是 變數指定:它宣告一個變數命名 express ,並將匹配 ExpressDelivery 的物件指派給該變數。
  • StandardDelivery { Days: <= 2 } 從類型測試開始。 StandardDelivery 測試評估值是否為該類型的非空物件。 大括號中包含屬性模式。 Days 命名要檢查的屬性,因此屬性 Days 表達式成為遞迴模式的輸入。 該 <= 2 部分為 關聯模式,用以測試評估值是否小於或等於 2。
  • _(底線)是 捨棄模式。 它匹配所有評估值,包括 null。 由於前面的分支已經處理 null、快速配送,以及兩天內送達的標準配送,因此最後一個分支會處理所有其餘值。

沒有 when 護腕的手臂是 無防護的。 第一個例子中的所有手臂均未設防。 如果較早且不含守衛條件的分支可匹配後面分支可能匹配到的每個已評估值,則後面的分支會被 涵蓋。 被涵蓋的模式,是指所有符合該模式的可能輸入值,都已經被先前的 switch 分支匹配。 它永遠無法匹配,所以編譯器會回報錯誤。 棄置臂(_)必須最後出現,因為它與所有輸入表達式相符。 護臂不會僅憑其模式就取代後面的護臂,因為護衛可能是 false。

當 switch 運算式的各個分支能處理所有可能的輸入運算式時,該運算式即為 exhaustive。 第一個例子是詳盡的,因為它的最後棄置臂處理了前幾隻臂不匹配的部分。 編譯器在偵測到未處理的輸入值時會警告。 編譯器無法證明每種模式組合的全面性,但這個診斷能幫助你撰寫正確的模式匹配程式碼。 關於詳細的匹配、吸收及窮盡性規則,請參閱 pattern 參考文獻。

測試單一條件 is

需要一個布林測試時就用這個 is 運算子。 以下程式碼評估表達式 delivery 並套用宣告模式 ExpressDelivery express。 當評估值非空且執行時型別相容 ExpressDelivery時,型別部分才會相符。 當模式符合時,其變數指定會宣告 express:

static void PrintTrackingCode(Delivery delivery)
{
    if (delivery is ExpressDelivery express)
    {
        Console.WriteLine($"Track express package {express.TrackingCode}");
    }
}

編譯器會在你的程式碼讀取區域變數之前,追蹤它是否被指派。 這種追蹤稱為 確定指派。 在 if 區塊內,只有當模式將相符物件指派給 true 時,條件才可以是 express。 因此編譯器知道該 express 確實被分配到那裡。 你的程式碼可以安全地使用它的 TrackingCode 屬性。 你不需要另外的石膏。 如果你只需要型別測試,不需要宣告變數,可以使用 型別模式,例如 delivery is ExpressDelivery。

使用 is null 或 is not null 用於空檢定。 這些模式不會呼叫使用者定義 == 或 != 運算子。

static void PrintPackageStatus(Delivery? delivery)
{
    if (delivery is null)
    {
        Console.WriteLine("No package is available.");
    }
    else
    {
        Console.WriteLine("A package is ready to track.");
    }
}

如需更多 Null 安全性的相關指引,請參見 可為 Null 的參考型別。

在陳述句與表達式之間選擇

模式適用於 switch 的兩種形式:

  • 當每個符合條件的情況都需要執行一個或多個陳述式時,請使用 switch 陳述式。
  • 當每個相符項目都應產生一個值時,請使用 switch 表達式。

以下交換語句報告交付更新。 快速遞送分支會寫兩則訊息,因此一個陳述句自然地適用:

static void PrintDeliveryUpdate(Delivery? delivery)
{
    switch (delivery)
    {
        case null:
            Console.WriteLine("No delivery was scheduled.");
            break;
        case ExpressDelivery express:
            Console.WriteLine($"Express delivery {express.TrackingCode} is ready.");
            Console.WriteLine("Notify the priority desk.");
            break;
        case StandardDelivery standard:
            Console.WriteLine($"Standard delivery arrives in {standard.Days} days.");
            break;
        default:
            Console.WriteLine("Another delivery type is scheduled.");
            break;
    }
}

每個 case 都會將模式套用至 delivery 運算式。 相符的區段可以在 break 離開 switch 之前執行任意數量的陳述式。 該 default 部門處理任何與早期案件不符的案件。

下列 switch 運算式會取代用來指派單一結果的 if / else if 鏈結:

static string GetStatusMessage(StandardDelivery delivery) =>
    delivery.Days switch
    {
        0 => "Delivered today",
        1 => "Arriving tomorrow",
        <= 3 => "Arriving soon",
        _ => "In transit"
    };

switch 運算式之所以簡潔,是因為每個分支都有相同的目的:產生要回傳的值。 當分支執行動作時使用 switch 陳述式,當分支計算一個結果時,使用 switch 表達式。

模式類別

C# 包含常見資料測試類型的模式:

模式類別 它測試什麼
宣言、恆常與 var 模式 執行時型別、特定的常數值,或任何你想捕捉的值
型態模式 一種未宣告變數的執行時型別
性質與位置模式 屬性、欄位或解構值
關聯型、邏輯型與括號型態 比較與組合,如 and、 or、 not
列表與切片模式 支撐序列的值與形狀
捨棄模式與捨棄項目 任何剩餘值,或是程式碼故意忽略的值

連結的《基礎》文章說明了何時選擇每個類別。 完整語法與範例請參見 patterns 參考。

另請參閱