共用方式為


11 模式和模式比對

11.1 一般

模式是一種語法形式,可與運算子 (§12.14.12)、switch_statement§13.8.3) 和switch_expression§12.11) 搭配is使用,以表示要比較傳入資料的資料形狀。 型樣可以是遞迴的,因此部分資料可以與 子型樣相符。

模式會針對許多內容中的值進行測試:

  • 在switch語句中,會針對switch語句的表示式測試案例標籤的型樣
  • is-pattern 運算子中,會針對左側的運算式測試右側的 模式
  • 在 switch 運算式中,會針對 switch-expression 左側的運算式測試switch_expression_arm模式
  • 在巢狀環境定義中,子 型樣 會針對從屬性、欄位擷取的值或從其他輸入值編製索引的值進行測試,視型樣形式而定。

測試型樣的值稱為 型樣輸入值

11.2 模式表單

11.2.1 一般

模式可能具有下列其中一種形式:

pattern
    : declaration_pattern
    | constant_pattern
    | var_pattern
    | positional_pattern
    | property_pattern
    | discard_pattern
    ;

某些 型樣s 可能會導致局部變數的宣告。

每個模式表單都會針對可套用模式的輸入值定義一組類型。 如果 模式是模式可能相符值的型別之一,則模式P適用於類型TT 如果程式中出現模式以符合類型的P模式輸入值(T),則為編譯時期錯誤,如果P不適用於 T

範例:下列範例會產生編譯時間錯誤,因為的編譯時間類型 vTextReader。 型 TextReader 別的變數永遠不能有與 string參考兼容的值:

TextReader v = Console.In; // compile-time type of 'v' is 'TextReader'
if (v is string) // compile-time error
{
    // code assuming v is a string
}

不過,下列不會產生編譯時間錯誤,因為的編譯時間類型 vobject。 類型的 object 變數可能會有與 參考相容的 string值:

object v = Console.In;
if (v is string s)
{
    // code assuming v is a string
}

end 範例

每個模式表單都會定義模式 符合 運行時間值的值集。

未指定模式比對期間評估作業及副作用的順序 (呼叫 Deconstruct、 屬性存取,以及 中 System.ITuple的方法呼叫)。

11.2.2 宣告模式

declaration_pattern可用來測試值是否具有給定類型,如果測試成功,則選擇性地在該類型的變數中提供值。

declaration_pattern
    : type simple_designation
    ;
simple_designation
    : discard_designation
    | single_variable_designation
    ;
discard_designation
    : '_'
    ;
single_variable_designation
    : identifier
    ;

帶有令牌_simple_designation應被視為discard_designation而不是single_variable_designation

值的執行階段類型會使用 is-type 運算子 (§12.14.12.1) 中指定的相同規則,針對模式中的類型進行測試。 如果測試成功,模式 符合該值。 如果 類型 是可為 Null 的值類型 (§8.3.12) 或可為 Null 的參考類型 (§8.9.3) ,則為編譯時間錯誤。 此模式表單永遠不會符合 null 值。

注意:當 不是可為 Null 的類型時e is T,is-type 運算式e is T _和宣告模式T就相等。 結尾註釋

給定模式輸入值 (§11.1e,如果 simple_designationdiscard_designation,則表示捨棄 (§9.2.9.2),且 e 的值不會系結至任何專案。 (雖然具有該名稱_的宣告變數可能在該點範圍內,但在此內容中看不到該具名變數。否則,如果simple_designation single_variable_designation,則會引進由指定識別碼命名的指定類型的局部變數 (§9.2.9)。 當模式符合值時,該局部變數會指派模式輸入值的值。

模式輸入值和指定型別的特定靜態類型組合會被視為不相容,並導致編譯時期錯誤。 如果存在身分識別轉換、隱含或明確參考轉換、Boxing 轉換或從 轉換為 的 Unboxing 轉換,或或為開放式類型 ,則靜態類型的E值會與。。TETE 宣告模式命名類型T適用於與 模式相容的每一種類型EET

注意:檢查可能是結構或類別類型的類型,並避免 Boxing 時,開啟類型的支援最有用。 結尾註釋

範例:宣告模式適用於執行參考型別的運行時間類型測試,並取代成語

var v = expr as Type;
if (v != null) { /* code using v */ }

稍微精簡一點

if (expr is Type v) { /* code using v */ }

end 範例

範例:宣告模式可用來測試可為 Null 型別的值:如果值為非 Null Nullable<T> 且 為 T,或是 的一些基底類型或介面T2 id,類型(或 BoxedT2) 的值T會比對類型模式T。 例如,在代碼段

int? x = 3;
if (x is int v) { /* code using v */ }

語句的條件if是在true運行時間,而變數v會保留 區塊內的 型別3int。 在區塊之後,變數 v 在作用域內,但未明確指派。 end 範例

11.2.3 常數模式

constant_pattern是用來測試模式輸入值(~11.1)與指定常數值的值。

constant_pattern
    : constant_expression
    ;

如果 常數表達式從的常數表達式隱含轉換成 型別,則常數模式P適用於T 類型PT

對於常數模式 P,其 轉換的值

  • 如果模式輸入值的型別是整數類型或列舉類型,則模式的常數值會轉換成該類型;否則
  • 如果模式輸入值的型別是整數型別或列舉類型的可為 Null 版本,則模式的常數值會轉換成其基礎類型;否則
  • 模式常數值的值。

假設模式輸入值 e 和具有已轉換值 P 的常數模式

  • 如果 e 具有整數型別或列舉型別,或是其中一種可為 Null 的形式,且 v 具有整數類型,則模式會比對表達式P的結果為 ,則比e == v對 etrue;否則為
  • 如果 傳回 ,則模式P會比對 eobject.Equals(e, v)true

範例switch 下列方法中的 語句會在其案例標籤中使用五個常數模式。

static decimal GetGroupTicketPrice(int visitorCount)
{
    switch (visitorCount) 
    {
        case 1: return 12.0m;
        case 2: return 20.0m;
        case 3: return 27.0m;
        case 4: return 32.0m;
        case 0: return 0.0m;
        default: throw new ArgumentException(...);
    }
}

end 範例

11.2.4 Var 模式

var_pattern 符合每個值。 也就是說,具有var_pattern模式比對作業一律會成功。

var_pattern適用於每個類型。

var_pattern
    : 'var' designation
    ;
designation
    : simple_designation
    | tuple_designation
    ;
tuple_designation
    : '(' designations? ')'
    ;
designations
    : designation (',' designation)*
    ;

給定模式輸入值 (§11.1e,如果指定discard_designation,則表示捨棄 (§9.2.9.2),且 e 的值不繫結於任何內容。 (雖然具有該名稱的宣告變數可能在該點範圍內,但在此內容中看不到該具名變數。否則,如果指定single_variable_designation,則在執行階段,e 的值會系結至該名稱的新引進的區域變數 (§9.2.9),其類型是 e 的靜態類型,而模式輸入值會指派給該區域變數。

如果名稱var系結至使用var_pattern的類型,就會發生錯誤。

如果名稱tuple_designation,則該圖案相當於形式(var名稱positional_pattern§11.2.5),... ) 其中 名稱s 是在 tuple_designation內找到的名稱。 例如,模式var (x, (y, z))(var x, (var y, var z))相當於 。

11.2.5 位置模式

positional_pattern檢查輸入值是否不是null,叫用適當的Deconstruct方法 (§12.7),並對結果值執行進一步的模式比對。 當輸入值的類型與包含 Deconstruct的類型相同時,它也支援類似元組的模式語法(不提供類型),或者如果輸入值的類型是元組類型,或者如果輸入值的類型是 objectSystem.ITuple ,並且表達式 System.ITuple的運行時類型實現。

positional_pattern
    : type? '(' subpatterns? ')' property_subpattern? simple_designation?
    ;
subpatterns
    : subpattern (',' subpattern)*
    ;
subpattern
    : pattern
    | identifier ':' pattern
    ;

如果輸入值與型樣 類型(子型樣)相符,則會選取方法,方法是在 類型 中搜尋 的 Deconstruct 可存取宣告,並使用與解構宣告相同的規則從中選取其中一個。 如果positional_pattern省略類型、具有沒有標識符的單一子模式、沒有property_subpattern且沒有simple_designation,則為錯誤。 這消除了括號內的 constant_patternpositional_pattern之間的歧義。 為了提取值以與清單中的模式相符,

  • 如果省略類型,且輸入運算式的類型是元組類型,則子模式的數目應與元組的基數相同。 每個元組元素都會與對應的 子模式進行比對,如果所有這些都成功,則比對成功。 如果任何 子模式 具有 標識符,則應在元組類型中相應的位置命名元組元素。
  • 否則,如果適當的 Deconstruct 存在為 類型的成員,則如果輸入值的類型與 類型不相容,則為編譯階段錯誤。 在執行階段,會針對 類型測試輸入值。 如果失敗,則位置型樣比對會失敗。 如果成功,輸入值會轉換成此類型,並 Deconstruct 使用新的編譯器產生變數來呼叫,以接收輸出參數。 收到的每個值都會與對應的 子模式進行比對,如果所有這些值都成功,則比對成功。 如果任何 子模式 具有 標識符,則應在 的 Deconstruct相應位置命名一個參數。
  • 否則 ,如果省 略類型,且輸入值是類型 object 或某些類型 System.ITuple ,可透過隱含參照轉換轉換,且子型樣中未出現任何 識別碼 ,則比對會使用 System.ITuple
  • 否則,模式是編譯時間錯誤。

未指定執行時期比對子型樣的順序,且失敗的比對可能不會嘗試比對所有子型樣。

範例:在這裡,我們解構一個表達式結果,並將結果值與對應的巢狀模式進行比對:

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

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

end 範例

範例:元組元素和解構參數的名稱可以在位置模式中使用,如下所示:

var numbers = new List<int> { 10, 20, 30 };
if (SumAndCount(numbers) is (Sum: var sum, Count: var count))
{
    Console.WriteLine($"Sum of [{string.Join(" ", numbers)}] is {sum}");
}

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

產生的輸出是

Sum of [10 20 30] is 60

end 範例

11.2.6 屬性模式

property_pattern會檢查輸入值是否不是 null,並遞迴比對使用可存取屬性或欄位所擷取的值。

property_pattern
    : type? property_subpattern simple_designation?
    ;
property_subpattern
    : '{' '}'
    | '{' subpatterns ','? '}'
    ;

如果property_pattern的任何子型樣不包含識別碼,則為錯誤。

如果 類型 是可為 Null 的值類型 (§8.3.12) 或可為 Null 的參考類型 (§8.9.3) ,則為編譯時間錯誤。

附註: 空值檢查模式會脫離簡單的屬性模式。 若要檢查字串 s 是否為非空值,可以編寫下列任何形式:

#nullable enable
string s = "abc";
if (s is object o) ...  // o is of type object
if (s is string x1) ... // x1 is of type string
if (s is {} x2) ...     // x2 is of type string
if (s is {}) ...

尾註假設運算式 e 與型樣類型{property_pattern_list} 相符,如果表示式 e類型所指定的類型 T 不相容,則為編譯時期錯誤。 如果類型不存在,則假設類型是 e 的靜態類型。 出現在其property_pattern_list左側的每個標識符都應指定一個可訪問的可讀屬性或 T 字段。如果存在property_patternsimple_designation,則會宣告類型為 T 的型樣變數。

在執行階段,會針對 T 測試運算式。如果失敗,則內容型樣比對會失敗,結果為 false。 如果成功,則會讀取每個 property_subpattern 欄位或屬性,並將其值與其對應的模式進行比對。 只有當其中任何一個的結果為 false時,整個比賽的結果才算false。 未指定子型樣比對的順序,且失敗的比對可能不會在執行時期測試所有子型樣。 如果比對成功,且property_patternsimple_designationsingle_variable_designation,則會為宣告的變數指派比對值。

property_pattern可用來與匿名類型進行模式比對。

範例:

var o = ...;
if (o is string { Length: 5 } s) ...

end 範例

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

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

產生的輸出是

Hello
Hi!
12345
abc

end 範例

11.2.7 丟棄模式

每個運算式都符合捨棄模式,這會導致捨棄運算式的值。

discard_pattern
    : '_'
    ;

relational_expression模式的形式中使用捨棄模式或作為switch_label模式的模式,這是編譯階段錯誤relational_expressionis

注意:在這些情況下,若要比對任何運算式,請使用帶有捨棄var _var_pattern結尾註釋

範例:

Console.WriteLine(GetDiscountInPercent(DayOfWeek.Friday));
Console.WriteLine(GetDiscountInPercent(null));
Console.WriteLine(GetDiscountInPercent((DayOfWeek)10));

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

產生的輸出是

5.0
0.0
0.0

在這裡,捨棄模式可用來處理 null 和任何沒有列舉對 DayOfWeek 應成員的整數值。 這保證 switch 運算式處理所有可能的輸入值。 end 範例

11.3 模式子建議

在 switch 語句中,如果案例的模式 是由先前的未受保值案例集所細分 ,就會發生錯誤(~13.8.3)。 非正式地說,這表示任何輸入值都會與上述其中一個案例相符。 下列規則會定義一組模式子化指定模式的時機:

如果該模式的運行時間行為規格符合 P ,則模式K對常數PK

如果下列任一條件保留,一組模式會Q子化模式P

  • P 是常數模式,而且集合 Q 中的任何模式都會符合 P轉換的值
  • P是 var 模式,而且模式集合Q對於模式輸入值的類型而言是詳盡的 (~11.4),而且模式輸入值不是可為 Null 的類型,或 中的Q某些模式會相符null
  • P是具有類型的T宣告模式,而且一組模式Q對於類型而言是T的({11.4)。

11.4 模式詳盡

在非 Null 的每個可能值中,如果集合中的某些模式適用,則一組模式對於類型而言是詳盡的。 下列規則會定義類型的一組模式 何時詳盡

如果下列任一條件保留,一組模式 Q詳盡說明 類型 T

  1. T 是整數或列舉型別,或其中一個可為 Null 的版本,而且對於 's non-nullable 基礎型別的每個可能值 T而言,中的 Q 某些模式會符合該值;或
  2. 中的 Q 某些模式為 var 模式;或
  3. 中的Q某些模式是 ,而且有識別轉換、隱含參考轉換,或從 D 轉換為T的Boxing轉換。

範例:

static void M(byte b)
{
    switch (b) {
        case 0: case 1: case 2: ... // handle every specific value of byte
            break;
        // error: the pattern 'byte other' is subsumed by the (exhaustive)
        // previous cases
        case byte other: 
            break;
    }
}

end 範例