模式比對概觀

模式比對 是一種技術,可讓您測試運算式,以判斷其是否有特定特性。 C# 模式比對提供更簡潔的語法,可用於測試運算式,並在運算式相符時採取動作。 「 is expression」 支援模式比對來測試運算式,並有條件地將該運算式的結果宣告新的變數。 「 switch expression」 可讓您根據運算式的第一個比對模式來執行動作。 這兩個運算式支援豐富的 模式詞彙。

本文提供您可以使用模式比對的案例概觀。 這些技術可以改善程式碼的可讀性和正確性。 如需您可以套用之所有模式的完整討論,請參閱語言參考中的 模式 文章。

Null 檢查

模式比對最常見的案例之一是確保值不是 null 。 您可以使用下列範例進行測試 null ,並將可為 Null 的實值型別轉換成其基礎型別:

int? maybe = 12;

if (maybe is int number)
{
    Console.WriteLine($"The nullable int 'maybe' has the value {number}");
}
else
{
    Console.WriteLine("The nullable int 'maybe' doesn't hold a value");
}

上述程式碼是測試變數類型的 宣告模式 ,並將它指派給新的變數。 語言規則可讓這項技術比許多其他技術更安全。 number變數只能在 子句的 true 部分中 if 存取並指派。 如果您嘗試在 子句或 else 區塊之後 if 的其他地方存取它,編譯器就會發出錯誤。 其次,因為您不是使用 == 運算子,所以當類型多載 == 運算子時,此模式會運作。 這可讓它成為檢查 Null 參考值的理想方式,新增 not 模式:

string? message = "This is not the null string";

if (message is not null)
{
    Console.WriteLine(message);
}

上述範例使用 常數模式 來比較變數與 nullnot邏輯模式,在否定模式不符合時符合。

類型測試

模式比對的另一個常見用法是測試變數,以查看它是否符合指定的類型。 例如,下列程式碼會測試變數是否為非 Null,並實作 System.Collections.Generic.IList<T> 介面。 如果這樣做,它會使用該 ICollection<T>.Count 清單上的 屬性來尋找中間索引。 不論變數的編譯時間類型為何,宣告模式都不符合 null 值。 下列程式碼除了 null 防止未實 IList 作 的類型之外,還會防護 。

public static T MidPoint<T>(IEnumerable<T> sequence)
{
    if (sequence is IList<T> list)
    {
        return list[list.Count / 2];
    }
    else if (sequence is null)
    {
        throw new ArgumentNullException(nameof(sequence), "Sequence can't be null.");
    }
    else
    {
        int halfLength = sequence.Count() / 2 - 1;
        if (halfLength < 0) halfLength = 0;
        return sequence.Skip(halfLength).First();
    }
}

在運算式中 switch 可以套用相同的測試,以針對多個不同類型的變數進行測試。 您可以使用該資訊,根據特定的執行時間類型來建立更好的演算法。

比較離散值

您也可以測試變數,以尋找特定值的相符專案。 下列程式碼示範一個範例,其中您會針對列舉中宣告的所有可能值測試值:

public State PerformOperation(Operation command) =>
   command switch
   {
       Operation.SystemTest => RunDiagnostics(),
       Operation.Start => StartSystem(),
       Operation.Stop => StopSystem(),
       Operation.Reset => ResetToReady(),
       _ => throw new ArgumentException("Invalid enum value for command", nameof(command)),
   };

上述範例會根據列舉的值來示範方法分派。 最後 _ 一個案例是符合所有值的 捨棄模式 。 它會處理值不符合其中一個已定義 enum 值的任何錯誤狀況。 如果您省略該切換 arm,編譯器會警告您尚未處理所有可能的輸入值。 在執行時間, switch 如果所檢查的物件不符合任何切換器,運算式就會擲回例外狀況。 您可以使用數值常數,而不是一組列舉值。 您也可以針對代表命令的常數位符串值使用這個類似的技術:

public State PerformOperation(string command) =>
   command switch
   {
       "SystemTest" => RunDiagnostics(),
       "Start" => StartSystem(),
       "Stop" => StopSystem(),
       "Reset" => ResetToReady(),
       _ => throw new ArgumentException("Invalid string value for command", nameof(command)),
   };

上述範例顯示相同的演算法,但會使用字串值,而不是列舉。 如果您的應用程式回應文字命令,而不是一般資料格式,您會使用此案例。 從 C# 11 開始,您也可以使用 Span<char>ReadOnlySpan<char> 來測試常數位符串值,如下列範例所示:

public State PerformOperation(ReadOnlySpan<char> command) =>
   command switch
   {
       "SystemTest" => RunDiagnostics(),
       "Start" => StartSystem(),
       "Stop" => StopSystem(),
       "Reset" => ResetToReady(),
       _ => throw new ArgumentException("Invalid string value for command", nameof(command)),
   };

在所有這些範例中, 捨棄模式 可確保您處理每個輸入。 編譯器可協助您確定處理每個可能的輸入值。

關聯式模式

您可以使用 關聯式模式 來測試值與常數的比較方式。 例如,下列程式碼會根據 Fahrenheit 中的溫度傳回水狀態:

string WaterState(int tempInFahrenheit) =>
    tempInFahrenheit switch
    {
        (> 32) and (< 212) => "liquid",
        < 32 => "solid",
        > 212 => "gas",
        32 => "solid/liquid transition",
        212 => "liquid / gas transition",
    };

上述程式碼也會示範結合邏輯 and模式,以檢查這兩種關係模式是否相符。 您也可以使用分離模式 or 來檢查任一模式是否相符。 這兩個關聯式模式會以括弧括住,您可以在任何模式周圍使用,以便清楚起見。 最後的兩個開關手會處理溫度點和氣氣點的案例。 如果沒有這兩個雙手,編譯器會警告您邏輯不會涵蓋每個可能的輸入。

上述程式碼也會示範編譯器為模式比對運算式提供的另一個重要功能:如果您未處理每個輸入值,編譯器會警告您。 如果先前的交換器 arm 已經處理交換器 arm,編譯器也會發出警告。 這可讓您自由地重構和重新排序參數運算式。 撰寫相同運算式的另一種方式可能是:

string WaterState2(int tempInFahrenheit) =>
    tempInFahrenheit switch
    {
        < 32 => "solid",
        32 => "solid/liquid transition",
        < 212 => "liquid",
        212 => "liquid / gas transition",
        _ => "gas",
};

在此課程中的主要課程,以及任何其他重構或重新排序,就是編譯器會驗證您已涵蓋所有輸入。

多重輸入

您到目前為止看到的所有模式都已檢查一個輸入。 您可以撰寫模式來檢查物件的多個屬性。 請考慮下列 Order 記錄:

public record Order(int Items, decimal Cost);

上述位置記錄類型會在明確的位置宣告兩個成員。 第一個出現是 Items ,然後是訂單的 Cost 。 如需詳細資訊,請參閱 記錄

下列程式碼會檢查項目數目和訂單的值,以計算折扣價格:

public decimal CalculateDiscount(Order order) =>
    order switch
    {
        { Items: > 10, Cost: > 1000.00m } => 0.10m,
        { Items: > 5, Cost: > 500.00m } => 0.05m,
        { Cost: > 250.00m } => 0.02m,
        null => throw new ArgumentNullException(nameof(order), "Can't calculate discount on null order"),
        var someObject => 0m,
    };

前兩個雙手會檢查 的 Order 兩個屬性。 第三個只會檢查成本。 下一個會針對 null 進行檢查,最後一個會比對任何其他值。 Order如果類型定義適當的 Deconstruct 方法,您可以從模式中省略屬性名稱,並使用解構來檢查屬性:

public decimal CalculateDiscount(Order order) =>
    order switch
    {
        ( > 10,  > 1000.00m) => 0.10m,
        ( > 5, > 50.00m) => 0.05m,
        { Cost: > 250.00m } => 0.02m,
        null => throw new ArgumentNullException(nameof(order), "Can't calculate discount on null order"),
        var someObject => 0m,
    };

上述程式碼示範運算式屬性解構 的位置模式

清單模式

您可以使用 清單模式來檢查清單中的元素或陣列。 清單模式提供將模式套用至序列的任何專案的方法。 此外,您可以套用 捨棄模式 (_) 以符合任何元素,或套用 配量模式 來比對零個或多個元素。

當資料未遵循一般結構時,清單模式是寶貴的工具。 您可以使用模式比對來測試資料的圖形和值,而不是將它轉換成一組物件。

請考慮下列摘錄自包含銀行交易的文字檔:

04-01-2020, DEPOSIT,    Initial deposit,            2250.00
04-15-2020, DEPOSIT,    Refund,                      125.65
04-18-2020, DEPOSIT,    Paycheck,                    825.65
04-22-2020, WITHDRAWAL, Debit,           Groceries,  255.73
05-01-2020, WITHDRAWAL, #1102,           Rent, apt, 2100.00
05-02-2020, INTEREST,                                  0.65
05-07-2020, WITHDRAWAL, Debit,           Movies,      12.57
04-15-2020, FEE,                                       5.55

它是 CSV 格式,但有些資料列的資料行比其他資料列多。 更糟的處理方式,類型中的 WITHDRAWAL 一個資料行具有使用者產生的文字,而且可以在文字中包含逗號。 包含捨棄模式、常數模式和var模式的清單模式,可擷取值以下列格式處理資料:

decimal balance = 0m;
foreach (string[] transaction in ReadRecords())
{
    balance += transaction switch
    {
        [_, "DEPOSIT", _, var amount]     => decimal.Parse(amount),
        [_, "WITHDRAWAL", .., var amount] => -decimal.Parse(amount),
        [_, "INTEREST", var amount]       => decimal.Parse(amount),
        [_, "FEE", var fee]               => -decimal.Parse(fee),
        _                                 => throw new InvalidOperationException($"Record {string.Join(", ", transaction)} is not in the expected format!"),
    };
    Console.WriteLine($"Record: {string.Join(", ", transaction)}, New balance: {balance:C}");
}

上述範例會採用字串陣列,其中每個元素都是資料列中的一個欄位。 第 switch 二個欄位的運算式索引鍵,決定交易的類型,以及剩餘的資料行數目。 每個資料列都會確保資料的格式正確。 捨棄 _ 模式 () 略過交易日期的第一個欄位。 第二個欄位符合交易的類型。 其餘專案會比對具有金額的欄位。 最終比對會使用 var 模式來擷取金額的字串表示。 運算式會計算要從餘額加入或減去的數量。

清單模式 可讓您比對資料元素序列的形狀。 您可以使用 捨棄配量 模式來比對專案的位置。 您可以使用其他模式來比對個別元素的特性。

本文提供您可以使用 C# 中的模式比對撰寫的程式碼類型導覽。 下列文章顯示更多在案例中使用模式的範例,以及可用模式的完整詞彙。

另請參閱