共用方式為


遞迴模式比對

注意

本文是功能規格。 規格可作為功能的設計檔。 其中包含建議的規格變更,以及功能設計和開發期間所需的資訊。 這些文章會發佈,直到提議的規格變更完成並併併入目前的ECMA規格為止。

功能規格與已完成實作之間可能有一些差異。 這些差異被記錄在 語言設計會議(LDM)的相關記錄中。

您可以在 規格的一篇文章中深入了解將功能規格小品採納為 C# 語言標準的過程

總結

C# 的模式匹配擴充功能使該語言能獲得功能語言中代數數據類型和模式匹配的許多優勢,同時順暢整合於其基於語言的特性中。 此方法的元素受到程式設計語言中的相關功能啟發,包括「可延伸模式比對透過輕量型語言」的 ,「比對物件與模式」的

詳細設計

是運算式

is 運算子被擴展來測試表示式是否符合 的模式

relational_expression
    : is_pattern_expression
    ;
is_pattern_expression
    : relational_expression 'is' pattern
    ;

此形式的 relational_expression 是對 C# 規範中現有表單的補充。 如果 is 標記左邊的 relational_expression 未指定值或沒有類型,則這是編譯時期錯誤。

模式的每個 標識符 都會引進新的局部變數,在 運算符 之後,絕對指派 變數(也就是在 true時絕對指派 )。

注意:從技術角度來看,類型is-expressionconstant_pattern之間有歧義,這兩者可能是對限定標識符的正確解析。 我們會嘗試將它綁定為與舊版語言相容的類型;只有當這樣失敗時,我們才會像在其他上下文中解析表達式一樣來解析它,解析為找到的第一個東西(這必須是常數或類型)。 這個模棱兩可只存在於 is 表達式的右側。

模式

模式用於 is_pattern 運算符、switch_statementswitch_expression 中,以描述要與輸入的數據(我們稱之為輸入值)比較的數據形狀。 模式可能會遞歸,以便數據的各個部分可能會與子模式進行比對。

pattern
    : declaration_pattern
    | constant_pattern
    | var_pattern
    | positional_pattern
    | property_pattern
    | discard_pattern
    ;
declaration_pattern
    : type simple_designation
    ;
constant_pattern
    : constant_expression
    ;
var_pattern
    : 'var' designation
    ;
positional_pattern
    : type? '(' subpatterns? ')' property_subpattern? simple_designation?
    ;
subpatterns
    : subpattern
    | subpattern ',' subpatterns
    ;
subpattern
    : pattern
    | identifier ':' pattern
    ;
property_subpattern
    : '{' '}'
    | '{' subpatterns ','? '}'
    ;
property_pattern
    : type? property_subpattern simple_designation?
    ;
simple_designation
    : single_variable_designation
    | discard_designation
    ;
discard_pattern
    : '_'
    ;

宣告模式

declaration_pattern
    : type simple_designation
    ;

declaration_pattern 用於測試表示式是否屬於指定類型,並在測試成功後將其轉換為該類型。 如果指定是 single_variable_designation,這可能會導入指定標識碼所命名之指定型別的局部變數。 該局部變數 當模式比對作業的結果是 true時,絕對會被指派

這個表達式的語意是,它會在運行時測試左側 relational_expression 操作數的運行時間類型是否符合模式中的 類型。 如果它是那種執行時期的類型(或某個子類型),並且不是 null,則 is operator 的結果為 true

左側靜態類型與指定類型的某些組合會被視為不相容,並可能導致編譯時期錯誤。 假設靜態類型 E 的值會與類型 T模式相容,如果有身分識別轉換、隱含參考轉換、Boxing 轉換、明確參考轉換,或從 ET的 unboxing 轉換,或其中一種類型是開放式類型,則為 。 如果類型 E 的輸入與 類型 的模式模式不 相容,則為編譯時期錯誤。

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

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

稍微更精簡

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

如果 類型 是可空實值型別,會發生錯誤。

類型模式可用來測試可為 Null 類型的值 Nullable<T>:如果值不是 null,且 T2 的類型是 TTT的一些基底類型或介面,則類型模式 T2 id 符合類型模式。 例如,在代碼段

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

if 語句的條件在運行時是 true,變數 v 在區塊內持有類型為 int 的值 3。 區塊之後,變數 v 位於範圍中,但未明確指派。

固定模式

constant_pattern
    : constant_expression
    ;

常數模式會針對常數值測試表達式的值。 常數可以是任何常數表達式,例如常值、宣告 const 變數的名稱或列舉常數。 當輸入值不是開放類型時,常數表達式會被隱式轉換成與之相匹配的表達式的類型。如果輸入值的類型與常數表達式的類型不符合模式的,則模式匹配操作會發生錯誤。

模式 c 被認為比對轉換後的輸入值 e 如果 object.Equals(c, e) 會傳回 true

我們預期 e is null 是新撰寫程式代碼中測試 null 最常見的方式,因為它無法叫用使用者定義的 operator==

Var 模式

var_pattern
    : 'var' designation
    ;
designation
    : simple_designation
    | tuple_designation
    ;
simple_designation
    : single_variable_designation
    | discard_designation
    ;
single_variable_designation
    : identifier
    ;
discard_designation
    : _
    ;
tuple_designation
    : '(' designations? ')'
    ;
designations
    : designation
    | designations ',' designation
    ;

如果 指定simple_designation,則表達式 e 符合模式。 換句話說,var 模式的比對, 一律會以 simple_designation成功。 如果 simple_designationsingle_variable_designation,則 e 的值會綁定至新引進的局部變數。 局部變數的類型是 e靜態類型。

如果 指定 是 tuple_designation,則模式相當於 指定格式positional_pattern... 其中 指定是在 tuple_designation中找到的。 例如,模式 var (x, (y, z)) 相當於 (var x, (var y, var z))

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

捨棄模式

discard_pattern
    : '_'
    ;

表達式 e 一律符合模式 _。 換句話說,每個運算式都符合捨棄模式。

捨棄模式不能當做 is_pattern_expression的模式使用。

位置模式

位置模式會檢查輸入值不是 null,然後叫用適當的 Deconstruct 方法,並對產生的值進行進一步的模式比對。 當輸入值的型別與包含 Deconstruct的類型相同,或是型別為 Tuple 類型,或是型別為 objectITuple 而且表達式在運行時期實作 ITuple時,它也支援類似 Tuple 的模式語法(不需提供型別)。

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

如果省略 類型,我們會將其當做輸入值的靜態類型。

假設輸入值符合模式 類型(子模式列表),會依循與解構宣告相同的規則,在 類型 中搜尋可存取的 Deconstruct 宣告,並從中選取一個方法。

如果 positional_pattern 省略類型、具有單一的 子模式 但沒有的 標識符、沒有 property_subpattern,並且沒有 simple_designation,這就是一個錯誤。 這可以區分一個附加括弧的 constant_pattern 和一個 positional_pattern

為了擷取值以符合清單中的模式,

  • 如果省略 類型,且輸入值的類型是元組類型,則子模式的數目必須與元組的元素個數相同。 每個元組元素都會與對應的 子模式進行匹配,若全部匹配成功,則整體匹配成功。 如果任何 子模式 具有 標識符,那麼它必須在元組類型中相應的位置命名為元組元素。
  • 否則,如果適當的 Deconstruct 存在於 類型的成員,如果輸入值的型別與 類型模式相容,則為編譯時期錯誤。 在運行時間,輸入值會針對 類型進行測試。 如果失敗,則位置模式比對會失敗。 如果成功,輸入值會轉換成此類型,並使用新的編譯程式產生的變數叫用 Deconstruct,以接收 out 參數。 收到的每個值都會與對應的 子模式進行比對,如果全部成功,比對就會成功。 如果任何 子模式 具有 標識子,那麼它必須在 Deconstruct的對應位置命名為一個參數。
  • 否則,如果省略了 類型,且輸入值的類型為 objectITuple 或某些類型可通過隱含參考轉換為 ITuple,而且在子模式中不會顯示任何 標識符,則我們會使用 ITuple進行比對。
  • 否則模式是編譯時期錯誤。

未指定運行時間中比對子模式的順序,且失敗的比對可能不會嘗試比對所有子模式。

此範例使用此規格中所述的許多功能

    var newState = (GetState(), action, hasKey) switch {
        (DoorState.Closed, Action.Open, _) => DoorState.Opened,
        (DoorState.Opened, Action.Close, _) => DoorState.Closed,
        (DoorState.Closed, Action.Lock, true) => DoorState.Locked,
        (DoorState.Locked, Action.Unlock, true) => DoorState.Closed,
        (var state, _, _) => state };

屬性模式

屬性模式會檢查輸入值不是 null,並以遞迴方式比對從可存取屬性或欄位中擷取的值。

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

如果 property_pattern 的任何 子模式 不包含 標識子,則為錯誤(其必須是具有 標識碼的第二種形式)。 最後一個子模式後面的尾端逗號是選擇性的。

請注意,Null 檢查模式會脫離簡單的屬性模式。 若要檢查字串 s 是否為非 Null,您可以撰寫下列任何表單

if (s is object o) ... // o is of type object
if (s is string x) ... // x is of type string
if (s is {} x) ... // x is of type string
if (s is {}) ...

假設表達式 e 與模式 類型property_pattern_list相符,如果表達式 e類型所指定的類型 T模式相容,則為編譯時期錯誤。 如果類型不存在,我們會將其設為 e e的靜態類型。 如果 識別碼 存在,它會宣告類型 類型的模式變數。 每個出現在其 property_pattern_list 左側的標識符,都必須指定可存取的可讀屬性或 T字段。如果 property_patternsimple_designation 存在,它會定義類型為 T的模式變數。

在運行時,會根據 T測試表示式。如果此測試失敗,則屬性模式比對失敗,結果為 false。 如果成功,將會讀取每個 property_subpattern 欄位或屬性,並將其值與相應的模式進行匹配。 只有當其中任何一項的結果是 false,整場比賽的結果才會是 false。 未指定子模式比對的順序,且失敗的比對可能不符合運行時間的所有子模式。 如果比對成功,且 property_patternsimple_designationsingle_variable_designation,它會定義一個 T 類型的變數 ,並將匹配的值指派給該變數。

注意:屬性模式可用來與匿名型別進行模式比對。

if (o is string { Length: 5 } s)

Switch 表達式

新增 switch_expression 以支援表達式內容的類似 switch語意。

C# 語言語法會隨著下列語法結構而擴充:

multiplicative_expression
    : switch_expression
    | multiplicative_expression '*' switch_expression
    | multiplicative_expression '/' switch_expression
    | multiplicative_expression '%' switch_expression
    ;
switch_expression
    : range_expression 'switch' '{' '}'
    | range_expression 'switch' '{' switch_expression_arms ','? '}'
    ;
switch_expression_arms
    : switch_expression_arm
    | switch_expression_arms ',' switch_expression_arm
    ;
switch_expression_arm
    : pattern case_guard? '=>' expression
    ;
case_guard
    : 'when' null_coalescing_expression
    ;

switch_expression 不允許作為 expression_statement

我們正在考慮在未來的修訂中放寬這一點。

switch_expression 的類型是 最佳常見類型{12.6.3.15) 表達式,如果這類類型存在,switch_expression_arm=> 標記右邊,而且參數表達式的每個臂中的表達式都可以隱含地轉換成該類型。 此外,我們會將新的 switch 運算式轉換新增至,這是從 switch 表達式預先定義的隱含轉換至每個類型 T,其中每個 arm 的運算式都有隱含轉換至 T

如果某些 switch_expression_arm模式無法影響結果,就會發生錯誤,因為某些先前的模式和防護一律會相符。

如果 switch 表達式的某些臂處理其輸入的每個值,則參數表示式會 詳盡。 如果參數表達式未完整 ,編譯程式應該會產生警告。

在運行時間,switch_expression 的結果是第一個 switch_expression_arm表達式的值,switch_expression 左側的 表達式符合 switch_expression_arm的模式,以及 switch_expression_armcase_guard。 如果存在,則評估為 true。 如果沒有這類 switch_expression_armswitch_expression 會擲回例外狀況的實例 System.Runtime.CompilerServices.SwitchExpressionException

在元組常值上使用可選括號進行切換

若要使用 switch_statement切換元組字面值,您必須撰寫看起來多餘的括號。

switch ((a, b))
{

允許

switch (a, b)
{

當 switch 語句中被操作的表達式是元組常值時,括號是可以省略的。

模式比對中的評估順序

給予編譯器在模式比對中重新排序操作的彈性,可以用來提升模式比對的效率。 未強制執行的需求是,模式中存取的屬性和解構方法需要是「純粹的」(無副作用、等冪性等)。 這並不表示我們會將純潔性新增為語言概念,只會允許編譯程式在重新排序作業時彈性。

解析 2018-04-04 LDM:已確認:編譯程式允許重新排序對 Deconstruct、屬性存取和 ITuple方法調用的呼叫,並可能假設傳回的值與多個呼叫相同。 編譯程式不應該叫用無法影響結果的函式,而且在未來對編譯程式產生的評估順序進行任何變更之前,我們將會非常小心。

某些可能的優化

模式比對的編譯過程可以利用模式的共同部分。 例如,如果 switch_statement 中連續兩個模式的最上層類型測試是相同的類型,則產生的程式代碼可以略過第二個模式的類型測試。

當某些模式是整數或字串時,編譯器可以生成它為舊版語言中 switch 語句所生成的相同類型的程式碼。

如需這類優化的詳細資訊,請參閱 [斯科特和拉姆齊(2000年)]