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 pattern:符合任何運算式,並將其結果指派給宣告的變數。 在 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 開始, T 當運算式結果為非 null,且下列任何條件成立時,具有類型的宣告模式會符合運算式:

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

  • 運算式結果的執行時間型別衍生自型別 T 、implements 介面 T 或另一個 隱含參考轉換 T 。 下列範例示範兩個條件成立的情況:

    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

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

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

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

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,且其執行時間類型符合上述任何條件時,型別模式會符合運算式。

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

常數模式

從 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)),
};

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

使用常數模式來檢查 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 或取消的轉換轉換成常數的型別,則關聯式模式不符合運算式。

如需詳細資訊,請參閱功能提案備註的 關聯式模式 一節。

邏輯模式

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

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

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

    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",
    };
    
  • Disjunctive 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}."),
    };
    

如先前的範例所示,您可以在模式中重複使用模式組合器。

and模式組合器的優先順序高於 or 。 若要明確指定優先順序,請使用括弧,如下列範例所示:

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

注意

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

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

屬性模式

從 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.0 開始,您可以參考屬性模式內的嵌套屬性或欄位。 例如,您可以將上述範例中的方法重構為下列對等程式碼:

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

如需詳細資訊,請參閱功能提案附注的 屬性模式 部分和 擴充屬性模式 功能提案注意事項。

位置模式

從 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",
};

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

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 和更新版本中提供的 關聯式邏輯 模式。

您可以在位置模式中使用元組元素和參數的名稱 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當您需要布林運算式內的暫存變數來保存中繼計算的結果時,模式會很有用。 var當您需要在運算式或語句的情況下執行額外的檢查時,您也可以使用模式 when switch ,如下列範例所示:

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 }
}

在上述範例中,pattern 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 運算式未處理所有可能的輸入值,則編譯器會產生警告。

捨棄模式不可以是 is 運算式或語句中的模式 switch 。 在這些情況下,若要比對任何運算式,請使用具有捨棄的 var 模式var _

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

括弧模式

從 c # 9.0 開始,您可以在任何模式前後加上括弧。 一般而言,您可以在 邏輯模式中強調或變更優先順序,如下列範例所示:

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

C# 語言規格

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

另請參閱