模式 (C# 參考)

C# 引進了 C# 7.0 中的模式比對。 之後,每個主要 C# 版本都會擴充模式比對功能。 下列 C# 運算式和語句支援模式比對:

在這些建構中,您可以比對輸入運算式與下列任何模式:

  • 宣告模式:若要檢查運算式的執行時間類型,如果相符專案成功,請將運算式結果指派給宣告的變數。 C# 7.0 引進。
  • 類型模式:檢查運算式的執行時間類型。 在 C# 9.0 中引進。
  • 常數模式:測試運算式結果是否等於指定的常數。 C# 7.0 引進。
  • 關聯式模式:比較運算式結果與指定的常數。 在 C# 9.0 中引進。
  • 邏輯模式:測試運算式是否符合模式的邏輯組合。 在 C# 9.0 中引進。
  • 屬性模式:測試運算式的屬性或欄位是否符合巢狀模式。 C# 8.0 引進。
  • 位置模式:解構運算式結果,並測試產生的值是否符合巢狀模式。 C# 8.0 引進。
  • var 模式:比對任何運算式,並將其結果指派給宣告的變數。 C# 7.0 引進。
  • 捨棄模式:以符合任何運算式。 C# 8.0 引進。

邏輯屬性位置 模式是 遞迴 模式。 也就是說,它們可以包含 巢狀 模式。

如需如何使用這些模式來建置資料驅動演算法的範例,請參閱 教學課程:使用模式比對來建置類型驅動和資料驅動演算法

宣告和類型模式

您可以使用宣告和類型模式來檢查運算式的執行時間類型是否與指定的類型相容。 使用宣告模式,您也可以宣告新的區域變數。 當宣告模式符合運算式時,該變數會指派已轉換的運算式結果,如下列範例所示:

object greeting = "Hello, World!";
if (greeting is string message)
{
    Console.WriteLine(message.ToLower());  // output: hello, world!
}

從 C# 7.0 開始,當運算式結果為非 Null 且下列任何條件成立時,類型 T 為 的宣告模式會比對運算式:

  • 運算式結果的執行時間類型為 T

  • 運算式結果的執行時間類型衍生自類型 T 、實作介面 T ,或從它到 T 的另一個隱含參考轉換。 下列範例示範此條件為 true 時的兩個案例:

    var numbers = new int[] { 10, 20, 30 };
    Console.WriteLine(GetSourceLabel(numbers));  // output: 1
    
    var letters = new List<char> { 'a', 'b', 'c', 'd' };
    Console.WriteLine(GetSourceLabel(letters));  // output: 2
    
    static int GetSourceLabel<T>(IEnumerable<T> source) => source switch
    {
        Array array => 1,
        ICollection<T> collection => 2,
        _ => 3,
    };
    

    在上述範例中,在第一次呼叫 GetSourceLabel 方法時,第一個模式會比對引數值,因為引數的執行時間類型 int[] 衍生自 Array 類型。 第二次呼叫 GetSourceLabel 方法時,引數的執行時間類型 List<T> 不會衍生自 型別, Array 而是實作 ICollection<T> 介面。

  • 運算式結果的執行時間類型是具有基礎型別 的 可為 Null 實值 型別 T

  • 從運算式結果的執行時間類型到 類型 T ,存在Boxingunboxing轉換。

下列範例示範最後兩個條件:

int? xNullable = 7;
int y = 23;
object yBoxed = y;
if (xNullable is int a && yBoxed is int b)
{
    Console.WriteLine(a + b);  // output: 30
}

如果您想要只檢查運算式的類型,您可以使用 discard _ 取代變數的名稱,如下列範例所示:

public abstract class Vehicle {}
public class Car : Vehicle {}
public class Truck : Vehicle {}

public static class TollCalculator
{
    public static decimal CalculateToll(this Vehicle vehicle) => vehicle switch
    {
        Car _ => 2.00m,
        Truck _ => 7.50m,
        null => throw new ArgumentNullException(nameof(vehicle)),
        _ => throw new ArgumentException("Unknown type of a vehicle", nameof(vehicle)),
    };
}

從 C# 9.0 開始,針對該用途,您可以使用 類型模式,如下列範例所示:

public static decimal CalculateToll(this Vehicle vehicle) => vehicle switch
{
    Car => 2.00m,
    Truck => 7.50m,
    null => throw new ArgumentNullException(nameof(vehicle)),
    _ => throw new ArgumentException("Unknown type of a vehicle", nameof(vehicle)),
};

如同宣告模式,當運算式結果為非 Null 且其執行時間類型滿足上述任何條件時,類型模式會比對運算式。

您也可以使用此模式進行清楚、簡潔 null 的檢查:

if (input is not null)
{
    Console.WriteLine(input);
} else
{
    throw new ArgumentNullException(paramName: nameof(input), message: "Input should not be null");
}

如需詳細資訊,請參閱功能提案附注的 宣告模式類型模式 小節。

常數模式

從 C# 7.0 開始,您可以使用 常數模式 來測試運算式結果是否等於指定的常數,如下列範例所示:

public static decimal GetGroupTicketPrice(int visitorCount) => visitorCount switch
{
    1 => 12.0m,
    2 => 20.0m,
    3 => 27.0m,
    4 => 32.0m,
    0 => 0.0m,
    _ => throw new ArgumentException($"Not supported number of visitors: {visitorCount}", nameof(visitorCount)),
};

在常數模式中,您可以使用任何常數運算式,例如:

運算式必須是可轉換成常數類型的類型,但有一個例外狀況:類型為 Span<char>ReadOnlySpan<char> 可比對 C# 11 和更新版本中常數位符串的運算式。

使用常數模式檢查 null ,如下列範例所示:

if (input is null)
{
    return;
}

編譯器保證在評估運算式 x is null 時,不會叫用任何使用者多載相等運算子 ==

從 C# 9.0 開始,您可以使用否定null 常數模式來檢查非 Null,如下列範例所示:

if (input is not null)
{
    // ...
}

如需詳細資訊,請參閱功能提案附注的 常數模式 一節。

關聯式模式

從 C# 9.0 開始,您可以使用 關係模式 來比較運算式結果與常數,如下列範例所示:

Console.WriteLine(Classify(13));  // output: Too high
Console.WriteLine(Classify(double.NaN));  // output: Unknown
Console.WriteLine(Classify(2.4));  // output: Acceptable

static string Classify(double measurement) => measurement switch
{
    < -4.0 => "Too low",
    > 10.0 => "Too high",
    double.NaN => "Unknown",
    _ => "Acceptable",
};

在關係模式中,您可以使用任何關係運算子<><=>= 。 關係模式的右手部分必須是常數運算式。 常數運算式可以是 整數浮點數、 char列舉 類型。

若要檢查運算式結果是否在特定範圍內,請將其與 結合 and 模式比對,如下列範例所示:

Console.WriteLine(GetCalendarSeason(new DateTime(2021, 3, 14)));  // output: spring
Console.WriteLine(GetCalendarSeason(new DateTime(2021, 7, 19)));  // output: summer
Console.WriteLine(GetCalendarSeason(new DateTime(2021, 2, 17)));  // output: winter

static string GetCalendarSeason(DateTime date) => date.Month switch
{
    >= 3 and < 6 => "spring",
    >= 6 and < 9 => "summer",
    >= 9 and < 12 => "autumn",
    12 or (>= 1 and < 3) => "winter",
    _ => throw new ArgumentOutOfRangeException(nameof(date), $"Date with unexpected month: {date.Month}."),
};

如果運算式結果為 null 或無法透過可為 Null 或 Unboxing 轉換轉換成常數的類型,關聯式模式與運算式不符。

如需詳細資訊,請參閱功能提案附注的關係 型模式 一節。

邏輯模式

從 C# 9.0 開始,您可以使用 notandor 模式組合器來建立下列 邏輯模式

  • 否定not當否定模式不符合運算式時,符合運算式的模式。 下列範例示範如何否定常數null 模式,以檢查運算式是否為非 Null:

    if (input is not null)
    {
        // ...
    }
    
  • Conjunctiveand當兩個模式都符合運算式時,符合運算式的模式。 下列範例示範如何結合 關係模式 來檢查某個值是否在特定範圍內:

    Console.WriteLine(Classify(13));  // output: High
    Console.WriteLine(Classify(-100));  // output: Too low
    Console.WriteLine(Classify(5.7));  // output: Acceptable
    
    static string Classify(double measurement) => measurement switch
    {
        < -40.0 => "Too low",
        >= -40.0 and < 0 => "Low",
        >= 0 and < 10.0 => "Acceptable",
        >= 10.0 and < 20.0 => "High",
        >= 20.0 => "Too high",
        double.NaN => "Unknown",
    };
    
  • 分隔or當任一模式符合運算式時,符合運算式的模式,如下列範例所示:

    Console.WriteLine(GetCalendarSeason(new DateTime(2021, 1, 19)));  // output: winter
    Console.WriteLine(GetCalendarSeason(new DateTime(2021, 10, 9)));  // output: autumn
    Console.WriteLine(GetCalendarSeason(new DateTime(2021, 5, 11)));  // output: spring
    
    static string GetCalendarSeason(DateTime date) => date.Month switch
    {
        3 or 4 or 5 => "spring",
        6 or 7 or 8 => "summer",
        9 or 10 or 11 => "autumn",
        12 or 1 or 2 => "winter",
        _ => throw new ArgumentOutOfRangeException(nameof(date), $"Date with unexpected month: {date.Month}."),
    };
    

如上述範例所示,您可以在模式中重複使用模式組合子。

檢查的優先順序和順序

下列清單會排序從最高優先順序到最低優先順序的模式組合器:

  • not
  • and
  • or

若要明確指定優先順序,請使用括弧,如下列範例所示:

static bool IsLetter(char c) => c is (>= 'a' and <= 'z') or (>= 'A' and <= 'Z');

注意

未定義檢查模式的順序。 在執行時間,可以先檢查 和 and 模式的 or 右手巢狀模式。

如需詳細資訊,請參閱功能提案附注的 模式組合器 一節。

屬性模式

從 C# 8.0 開始,您可以使用 屬性模式 來比對運算式的屬性或欄位與巢狀模式,如下列範例所示:

static bool IsConferenceDay(DateTime date) => date is { Year: 2020, Month: 5, Day: 19 or 20 or 21 };

當運算式結果為非 Null 且每個巢狀模式符合運算式結果的對應屬性或欄位時,屬性模式就會比對運算式。

您也可以將執行時間類型檢查和變數宣告新增至屬性模式,如下列範例所示:

Console.WriteLine(TakeFive("Hello, world!"));  // output: Hello
Console.WriteLine(TakeFive("Hi!"));  // output: Hi!
Console.WriteLine(TakeFive(new[] { '1', '2', '3', '4', '5', '6', '7' }));  // output: 12345
Console.WriteLine(TakeFive(new[] { 'a', 'b', 'c' }));  // output: abc

static string TakeFive(object input) => input switch
{
    string { Length: >= 5 } s => s.Substring(0, 5),
    string s => s,

    ICollection<char> { Count: >= 5 } symbols => new string(symbols.Take(5).ToArray()),
    ICollection<char> symbols => new string(symbols.ToArray()),

    null => throw new ArgumentNullException(nameof(input)),
    _ => throw new ArgumentException("Not supported input type."),
};

屬性模式是遞迴模式。 也就是說,您可以使用任何模式作為巢狀模式。 使用屬性模式來比對巢狀模式的資料部分,如下列範例所示:

public record Point(int X, int Y);
public record Segment(Point Start, Point End);

static bool IsAnyEndOnXAxis(Segment segment) =>
    segment is { Start: { Y: 0 } } or { End: { Y: 0 } };

上述範例使用 C# 9.0 和更新版本中提供的兩個功能: or模式組合器和記錄類型

從 C# 10 開始,您可以參考屬性模式中的巢狀屬性或欄位。 這稱為 擴充屬性模式。 例如,您可以將上述範例中的 方法重構為下列對等程式碼:

static bool IsAnyEndOnXAxis(Segment segment) =>
    segment is { Start.Y: 0 } or { End.Y: 0 };

如需詳細資訊,請參閱功能提案附注的 屬性模式 一節和 擴充屬性模式 功能提案附注。

秘訣

您可以使用 簡化屬性模式 (IDE0170) 樣式規則,藉由建議使用擴充屬性模式的位置來改善程式碼可讀性。

位置模式

從 C# 8.0 開始,您可以使用 位置模式 來解構運算式結果,並將產生的值與對應的巢狀模式相符,如下列範例所示:

public readonly struct Point
{
    public int X { get; }
    public int Y { get; }

    public Point(int x, int y) => (X, Y) = (x, y);

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

static string Classify(Point point) => point switch
{
    (0, 0) => "Origin",
    (1, 0) => "positive X basis end",
    (0, 1) => "positive Y basis end",
    _ => "Just a point",
};

在上述範例中,運算式的類型包含 解構 方法,用來解構運算式結果。 您也可以比對 Tuple 類型的 運算式與位置模式。 如此一來,您就可以根據各種模式比對多個輸入,如下列範例所示:

static decimal GetGroupTicketPriceDiscount(int groupSize, DateTime visitDate)
    => (groupSize, visitDate.DayOfWeek) switch
    {
        (<= 0, _) => throw new ArgumentException("Group size must be positive."),
        (_, DayOfWeek.Saturday or DayOfWeek.Sunday) => 0.0m,
        (>= 5 and < 10, DayOfWeek.Monday) => 20.0m,
        (>= 10, DayOfWeek.Monday) => 30.0m,
        (>= 5 and < 10, _) => 12.0m,
        (>= 10, _) => 15.0m,
        _ => 0.0m,
    };

上述範例使用 C# 9.0 和更新版本中可用的 關係 型和 邏輯 模式。

您可以在位置模式中使用 Tuple 元素和 Deconstruct 參數的名稱,如下列範例所示:

var numbers = new List<int> { 1, 2, 3 };
if (SumAndCount(numbers) is (Sum: var sum, Count: > 0))
{
    Console.WriteLine($"Sum of [{string.Join(" ", numbers)}] is {sum}");  // output: Sum of [1 2 3] is 6
}

static (double Sum, int Count) SumAndCount(IEnumerable<int> numbers)
{
    int sum = 0;
    int count = 0;
    foreach (int number in numbers)
    {
        sum += number;
        count++;
    }
    return (sum, count);
}

您也可以使用下列任何方式來擴充位置模式:

  • 新增執行時間類型檢查和變數宣告,如下列範例所示:

    public record Point2D(int X, int Y);
    public record Point3D(int X, int Y, int Z);
    
    static string PrintIfAllCoordinatesArePositive(object point) => point switch
    {
        Point2D (> 0, > 0) p => p.ToString(),
        Point3D (> 0, > 0, > 0) p => p.ToString(),
        _ => string.Empty,
    };
    

    上述範例會使用隱含提供 Deconstruct 方法的位置記錄

  • 在位置 模式中使用屬性模式 ,如下列範例所示:

    public record WeightedPoint(int X, int Y)
    {
        public double Weight { get; set; }
    }
    
    static bool IsInDomain(WeightedPoint point) => point is (>= 0, >= 0) { Weight: >= 0.0 };
    
  • 結合上述兩個用法,如下列範例所示:

    if (input is WeightedPoint (> 0, > 0) { Weight: > 0.0 } p)
    {
        // ..
    }
    

位置模式是遞迴模式。 也就是說,您可以使用任何模式作為巢狀模式。

如需詳細資訊,請參閱功能提案附注的位置 模式 一節。

var 模式

從 C# 7.0 開始,您可以使用var 模式來比對任何運算式,包括 null ,並將其結果指派給新的區域變數,如下列範例所示:

static bool IsAcceptable(int id, int absLimit) =>
    SimulateDataFetch(id) is var results 
    && results.Min() >= -absLimit 
    && results.Max() <= absLimit;

static int[] SimulateDataFetch(int id)
{
    var rand = new Random();
    return Enumerable
               .Range(start: 0, count: 5)
               .Select(s => rand.Next(minValue: -10, maxValue: 11))
               .ToArray();
}

var當您在布林運算式內需要暫存變數來保存中繼計算結果時,模式非常有用。 如果您需要在運算式或語句的 switch 防護中 when 執行其他檢查,您也可以使用 var 模式,如下列範例所示:

public record Point(int X, int Y);

static Point Transform(Point point) => point switch
{
    var (x, y) when x < y => new Point(-x, y),
    var (x, y) when x > y => new Point(x, -y),
    var (x, y) => new Point(x, y),
};

static void TestTransform()
{
    Console.WriteLine(Transform(new Point(1, 2)));  // output: Point { X = -1, Y = 2 }
    Console.WriteLine(Transform(new Point(5, 2)));  // output: Point { X = 5, Y = -2 }
}

在上述範例中,模式 var (x, y) 相當於位置模式(var x, var y)

var在模式中,宣告變數的類型是與模式相符之運算式的編譯時間類型。

如需詳細資訊,請參閱功能提案附注的 Var 模式 一節。

捨棄模式

從 C# 8.0 開始,您可以使用捨棄模式_ 來比對任何運算式,包括 null ,如下列範例所示:

Console.WriteLine(GetDiscountInPercent(DayOfWeek.Friday));  // output: 5.0
Console.WriteLine(GetDiscountInPercent(null));  // output: 0.0
Console.WriteLine(GetDiscountInPercent((DayOfWeek)10));  // output: 0.0

static decimal GetDiscountInPercent(DayOfWeek? dayOfWeek) => dayOfWeek switch
{
    DayOfWeek.Monday => 0.5m,
    DayOfWeek.Tuesday => 12.5m,
    DayOfWeek.Wednesday => 7.5m,
    DayOfWeek.Thursday => 12.5m,
    DayOfWeek.Friday => 5.0m,
    DayOfWeek.Saturday => 2.5m,
    DayOfWeek.Sunday => 2.0m,
    _ => 0.0m,
};

在上述範例中,會使用捨棄模式來處理 null ,以及沒有列舉對應成員 DayOfWeek 的任何整數值。 這可確保 switch 範例中的運算式會處理所有可能的輸入值。 如果您未在運算式中使用 switch 捨棄模式,且運算式的模式都不符合輸入,執行時間 會擲回例外狀況。 如果 switch 運算式未處理所有可能的輸入值,編譯器會產生警告。

捨棄模式不能是運算式或 switch 語句中的 is 模式。 在這些情況下,若要比對任何運算式,請使用var 模式搭配 discard: var _

如需詳細資訊,請參閱功能提案附注的 捨棄模式 一節。

括弧化模式

從 C# 9.0 開始,您可以將括弧放在任何模式周圍。 一般而言,您會這麼做,以強調或變更 邏輯模式中的優先順序,如下列範例所示:

if (input is not (float or double))
{
    return;
}

清單模式

從 C# 11 開始,您可以將陣列或清單與符合元素的模式 序列 比對。 您可以套用下列任何模式:

  • 任何模式都可以套用至任何專案,以檢查個別專案是否符合特定特性。
  • 捨棄模式 () _ 符合單一元素。
  • 範圍模式 (..) 可以比對序列中的零個或多個元素。 清單模式最多允許一個範圍模式。
  • var 模式可以擷取單一元素或元素範圍。

這些規則會使用下列陣列宣告來示範:

int[] one = { 1 };
int[] odd = { 1, 3, 5 };
int[] even = { 2, 4, 6 };
int[] fib = { 1, 1, 2, 3, 5 };

您可以指定所有元素並使用值來比對整個序列:

Console.WriteLine(odd is [1, 3, 5]); // true
Console.WriteLine(even is [1, 3, 5]); // false (values)
Console.WriteLine(one is [1, 3, 5]); // false (length)

您可以使用捨棄模式來比對已知長度序列中的某些元素, _ () 做為預留位置:

Console.WriteLine(odd is [1, _, _]); // true
Console.WriteLine(odd is [_, 3, _]); // true
Console.WriteLine(even is [_, _, 5]); // false (last value)

您可以在序列中的任何位置提供任意數目的值或預留位置。 如果您不在意長度,您可以使用 範圍 模式來比對零個或多個元素:

Console.WriteLine(odd is [1, .., 3, _]); // true
Console.WriteLine(fib is [1, .., 3, _]); // true

Console.WriteLine(odd is [1, _, 5, ..]); // true
Console.WriteLine(fib is [1, _, 5, ..]); // false

先前的範例使用 常數模式 來判斷專案是否為指定的數位。 這些模式的任何一種都可以由不同的模式取代,例如關聯式模式:

Console.WriteLine(odd is [_, >1, ..]); // true
Console.WriteLine(even is [_, >1, ..]); // true
Console.WriteLine(fib is [_, > 1, ..]); // false

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

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

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 (var 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 {transaction} is not in the expected format!"),
    };
    Console.WriteLine($"Record: {transaction}, New balance: {balance:C}");
}

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

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

C# 語言規格

如需詳細資訊,請參閱下列功能提案附注:

另請參閱