遞迴模式比對Recursive Pattern Matching
總結Summary
適用于 c # 的模式比對延伸模組可讓您從功能性語言取得代數資料類型和模式比對的許多優點,但這種方式可與基礎語言的風格順暢地整合。Pattern matching extensions for C# enable many of the benefits of algebraic data types and pattern matching from functional languages, but in a way that smoothly integrates with the feel of the underlying language. 這種方法的元素是由程式設計語言 F # 和 Scala中的相關功能所靈感。Elements of this approach are inspired by related features in the programming languages F# and Scala.
詳細設計Detailed design
為運算式Is Expression
is
系統會擴充運算子,以根據 模式 來測試運算式。The is
operator is extended to test an expression against a pattern.
relational_expression
: is_pattern_expression
;
is_pattern_expression
: relational_expression 'is' pattern
;
這種形式的 relational_expression 除了 c # 規格中的現有表單之外。This form of relational_expression is in addition to the existing forms in the C# specification. 如果 token 左邊的 relational_expression is
未指定值或沒有類型,就會發生編譯時期錯誤。It is a compile-time error if the relational_expression to the left of the is
token does not designate a value or does not have a type.
模式的每個 識別碼 都會導入一個新的區域變數,這個 區域變數是在 is
運算子 true
((亦即, 在 true) 時明確指派 )。Every identifier of the pattern introduces a new local variable that is definitely assigned after the is
operator is true
(i.e. definitely assigned when true).
注意:在技術上,和 constant_pattern 中的 類型 之間會有不明確的情況
is-expression
,可能是限定識別碼的有效剖析。 Note: There is technically an ambiguity between type in anis-expression
and constant_pattern, either of which might be a valid parse of a qualified identifier. 我們會嘗試將其系結為類型,以便與舊版的語言相容;只有當它失敗時,我們會解決它,因為我們會在其他內容中進行運算式,以找出 (必須是常數或) 類型的第一個內容。We try to bind it as a type for compatibility with previous versions of the language; only if that fails do we resolve it as we do an expression in other contexts, to the first thing found (which must be either a constant or a type). 這種不明確的只存在於運算式的右手邊is
。This ambiguity is only present on the right-hand-side of anis
expression.
模式Patterns
在 is_pattern 運算子、 switch_statement 中,以及 switch_expression 中使用模式來表示資料的圖形,而這些資料 (我們呼叫輸入值) 要進行比較。Patterns are used in the is_pattern operator, in a switch_statement, and in a switch_expression to express the shape of data against which incoming data (which we call the input value) is to be compared. 模式可能是遞迴的,因此資料的部分可能會與子模式進行比對。Patterns may be recursive so that parts of the data may be matched against sub-patterns.
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
declaration_pattern
: type simple_designation
;
Declaration_pattern 會測試運算式是否為指定的型別,並在測試成功時將它轉換成該型別。The declaration_pattern both tests that an expression is of a given type and casts it to that type if the test succeeds. 如果指定是 single_variable_designation,這可能會導入給定識別碼所命名之指定類型的本機變數。This may introduce a local variable of the given type named by the given identifier, if the designation is a single_variable_designation. 當模式比對作業的結果為時,就會 明確地指派 該本機變數 true
。That local variable is definitely assigned when the result of the pattern-matching operation is true
.
此運算式的執行時間語義是它會根據模式中的 型 別,測試左邊 relational_expression 運算元的執行時間型別。The runtime semantic of this expression is that it tests the runtime type of the left-hand relational_expression operand against the type in the pattern. 如果該執行時間類型 (或某些子類型) 而不是 null
,則的結果 is operator
為 true
。If it is of that runtime type (or some subtype) and not null
, the result of the is operator
is true
.
左邊和指定型別的靜態型別的某些組合會視為不相容,而且會導致編譯階段錯誤。Certain combinations of static type of the left-hand-side and the given type are considered incompatible and result in compile-time error. E
如果有身分 T
識別轉換、隱含參考轉換、裝箱轉換、明確參考轉換,或從轉換為的取消的轉換, E
T
或其中一個類型是開放式型別,則靜態類型的值會被視為與型別的模式相容。A value of static type E
is said to be pattern-compatible with a type T
if there exists an identity conversion, an implicit reference conversion, a boxing conversion, an explicit reference conversion, or an unboxing conversion from E
to T
, or if one of those types is an open type. 如果類型的輸入與符合的型別 E
模式中的 型 別不 相容,就會發生編譯時期錯誤。It is a compile-time error if an input of type E
is not pattern-compatible with the type in a type pattern that it is matched with.
型別模式適用于執行參考型別的執行時間類型測試,並取代用法The type pattern is useful for performing run-time type tests of reference types, and replaces the idiom
var v = expr as Type;
if (v != null) { // code using v
稍微簡單一點With the slightly more concise
if (expr is Type v) { // code using v
如果 型 別是可為 null 的實值型別,就會發生錯誤。It is an error if type is a nullable value type.
型別模式可用來測試可為 null 型別的值: Nullable<T>
T
T2 id
如果值為非 null、的型別 T2
為,或的型別為 T
,或的某些基底類型或介面, T
則會符合型別 (的值或已封裝的) 。The type pattern can be used to test values of nullable types: a value of type Nullable<T>
(or a boxed T
) matches a type pattern T2 id
if the value is non-null and the type of T2
is T
, or some base type or interface of T
. 例如,在程式碼片段中For example, in the code fragment
int? x = 3;
if (x is int v) { // code using v
語句的條件 if
是 true
在執行時間,而變數會在 v
3
區塊內保存類型的值 int
。The condition of the if
statement is true
at runtime and the variable v
holds the value 3
of type int
inside the block. 在區塊之後,變數 v
是在範圍內,但未明確指派。After the block the variable v
is in scope but not definitely assigned.
常數模式Constant Pattern
constant_pattern
: constant_expression
;
常數模式會針對常數值測試運算式的值。A constant pattern tests the value of an expression against a constant value. 常數可以是任何常數運算式,例如常值、宣告的 const
變數名稱或列舉常數。The constant may be any constant expression, such as a literal, the name of a declared const
variable, or an enumeration constant. 當輸入值不是開放式型別時,常數運算式會隱含地轉換成相符運算式的型別。如果輸入值的型別與常數運算式的型別不 相容 ,則模式比對作業是錯誤。When the input value is not an open type, the constant expression is implicitly converted to the type of the matched expression; if the type of the input value is not pattern-compatible with the type of the constant expression, the pattern-matching operation is an error.
如果傳回,則會將模式 c 視為符合轉換的輸入值 e object.Equals(c, e)
true
。The pattern c is considered matching the converted input value e if object.Equals(c, e)
would return true
.
e is null
在新撰寫的程式碼中,我們預期會看到最常見的測試方法 null
,因為它無法叫用使用者定義的程式碼 operator==
。We expect to see e is null
as the most common way to test for null
in newly written code, as it cannot invoke a user-defined operator==
.
Var 模式Var Pattern
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 會符合模式。If the designation is a simple_designation, an expression e matches the pattern. 換句話說,對 var 模式 的比對一律會成功,且 simple_designation。In other words, a match to a var pattern always succeeds with a simple_designation. 如果 simple_designation 是 single_variable_designation,則 e 的值會系結至新引進的本機變數。If the simple_designation is a single_variable_designation, the value of e is bounds to a newly introduced local variable. 本機變數的類型是 e 的靜態類型。The type of the local variable is the static type of e.
如果 指定 為 tuple_designation,則模式相當於格式指定的 positional_pattern ,也就是在 (var
)
tuple_designation 中找到的 指定。If the designation is a tuple_designation, then the pattern is equivalent to a positional_pattern of the form (var
designation, ... )
where the designation s are those found within the tuple_designation. 例如,此模式 var (x, (y, z))
相當於 (var x, (var y, var z))
。For example, the pattern var (x, (y, z))
is equivalent to (var x, (var y, var z))
.
如果名稱系結至類型,就會發生錯誤 var
。It is an error if the name var
binds to a type.
捨棄模式Discard Pattern
discard_pattern
: '_'
;
運算式 e 會一律符合模式 _
。An expression e matches the pattern _
always. 換句話說,每個運算式都符合捨棄模式。In other words, every expression matches the discard pattern.
捨棄模式不可以做為 is_pattern_expression 的模式使用。A discard pattern may not be used as the pattern of an is_pattern_expression.
位置模式Positional Pattern
位置模式會檢查輸入值是否不是、叫用 null
適當的 Deconstruct
方法,並在產生的值上執行進一步的模式比對。A positional pattern checks that the input value is not null
, invokes an appropriate Deconstruct
method, and performs further pattern matching on the resulting values. 它也支援類似元組的模式語法 (沒有提供的型別) 當輸入值的型別與包含的型別相同,或者輸入值的型別是元組型別時,或是輸入值的型別是 Deconstruct
object
或 ITuple
,以及運算式的執行時間型別 ITuple
。It also supports a tuple-like pattern syntax (without the type being provided) when the type of the input value is the same as the type containing Deconstruct
, or if the type of the input value is a tuple type, or if the type of the input value is object
or ITuple
and the runtime type of the expression implements ITuple
.
positional_pattern
: type? '(' subpatterns? ')' property_subpattern? simple_designation?
;
subpatterns
: subpattern
| subpattern ',' subpatterns
;
subpattern
: pattern
| identifier ':' pattern
;
如果省略 類型 ,則會將它視為輸入值的靜態類型。If the type is omitted, we take it to be the static type of the input value.
假設有一個輸入值符合模式 類型 (
subpattern_list )
,則會藉由搜尋 類型 中的可存取宣告來選取方法, Deconstruct
並使用與解構宣告相同的規則來選取其中一個方法。Given a match of an input value to the pattern type (
subpattern_list )
, a method is selected by searching in type for accessible declarations of Deconstruct
and selecting one among them using the same rules as for the deconstruction declaration.
如果 positional_pattern 省略類型、具有沒有 識別碼 的單一子 模式、沒有 property_subpattern 且沒有 simple_designation,就會發生錯誤。It is an error if a positional_pattern omits the type, has a single subpattern without an identifier, has no property_subpattern and has no simple_designation. 這種厘清是以括弧括住的 constant_pattern 與 positional_pattern。This disambiguates between a constant_pattern that is parenthesized and a positional_pattern.
為了將值解壓縮以符合清單中的模式,In order to extract the values to match against the patterns in the list,
- 如果省略 型 別,且輸入值的型別是元組型別,則 subpatterns 的數目必須和元組的基數相同。If type was omitted and the input value's type is a tuple type, then the number of subpatterns is required to be the same as the cardinality of the tuple. 每個元組元素都會與相對應的子 模式 進行比對,而如果全部成功,則比對成功。Each tuple element is matched against the corresponding subpattern, and the match succeeds if all of these succeed. 如果有任何子 模式 具有 識別碼,則必須將元組元素命名為元組類型中的對應位置。If any subpattern has an identifier, then that must name a tuple element at the corresponding position in the tuple type.
- 否則,如果適合以
Deconstruct
類型 的成員的形式存在,則如果輸入值的類型與 類型 不 相容,就會發生編譯時期錯誤。Otherwise, if a suitableDeconstruct
exists as a member of type, it is a compile-time error if the type of the input value is not pattern-compatible with type. 在執行時間,輸入值會針對 類型 進行測試。At runtime the input value is tested against type. 如果失敗,則位置模式比對會失敗。If this fails then the positional pattern match fails. 如果成功,則會將輸入值轉換成這個類型,並Deconstruct
使用全新編譯器產生的變數來叫用來接收out
參數。If it succeeds, the input value is converted to this type andDeconstruct
is invoked with fresh compiler-generated variables to receive theout
parameters. 收到的每個值會與相對應的子 模式 進行比對,而如果全部成功,則比對成功。Each value that was received is matched against the corresponding subpattern, and the match succeeds if all of these succeed. 如果有任何子 模式 具有 識別碼,則必須將參數命名于對應的位置Deconstruct
。If any subpattern has an identifier, then that must name a parameter at the corresponding position ofDeconstruct
. - 如果省略了 類型 ,而且輸入值的類型是
object
或ITuple
可由隱含參考轉換轉換成的類型,ITuple
而且 subpatterns 中不會出現任何 識別碼 ,則會使用進行比對ITuple
。Otherwise if type was omitted, and the input value is of typeobject
orITuple
or some type that can be converted toITuple
by an implicit reference conversion, and no identifier appears among the subpatterns, then we match usingITuple
. - 否則,模式會是編譯時期錯誤。Otherwise the pattern is a compile-time error.
未指定 subpatterns 在執行時間中相符的順序,且失敗的相符可能不會嘗試比對所有 subpatterns。The order in which subpatterns are matched at runtime is unspecified, and a failed match may not attempt to match all subpatterns.
範例Example
此範例會使用此規格中所述的許多功能。This example uses many of the features described in this specification
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 };
屬性模式Property Pattern
屬性模式會檢查輸入值是否不會 null
遞迴符合使用可存取的屬性或欄位所解壓縮的值。A property pattern checks that the input value is not null
and recursively matches values extracted by the use of accessible properties or fields.
property_pattern
: type? property_subpattern simple_designation?
;
property_subpattern
: '{' '}'
| '{' subpatterns ','? '}'
;
如果 property_pattern 的任何子 模式 不包含 識別碼,就會發生錯誤 (它必須是第二個表單,其 識別碼) 。It is an error if any subpattern of a property_pattern does not contain an identifier (it must be of the second form, which has an identifier). 最後一個子模式之後的尾端逗號是選擇性的。A trailing comma after the last subpattern is optional.
請注意,null 檢查模式會超出一般屬性模式。Note that a null-checking pattern falls out of a trivial property pattern. 若要檢查字串 s
是否為非 null,您可以撰寫下列任何格式To check if the string s
is non-null, you can write any of the following forms
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 不 相容,就會發生編譯時期錯誤。Given a match of an expression e to the pattern type {
property_pattern_list }
, it is a compile-time error if the expression e is not pattern-compatible with the type T designated by type. 如果類型不存在,我們會將它視為 e 的靜態類型。If the type is absent, we take it to be the static type of e. 如果 識別碼 存在,則會宣告 類型類型的模式變數。If the identifier is present, it declares a pattern variable of type type. 每個出現在其 property_pattern_list 左邊的識別碼,都必須指定可存取的可讀取屬性或 T 的欄位。如果 property_pattern 的 simple_designation 存在,它會定義 T 類型的模式變數。Each of the identifiers appearing on the left-hand-side of its property_pattern_list must designate an accessible readable property or field of T. If the simple_designation of the property_pattern is present, it defines a pattern variable of type T.
在執行時間,運算式會針對 T 進行測試。如果失敗,則屬性模式比對會失敗,並產生結果 false
。At runtime, the expression is tested against T. If this fails then the property pattern match fails and the result is false
. 如果成功,則會讀取每個 property_subpattern 欄位或屬性,且其值會與其對應的模式相符。If it succeeds, then each property_subpattern field or property is read and its value matched against its corresponding pattern. 整個比對的結果只有在 false
其中任何一項的結果為時 false
。The result of the whole match is false
only if the result of any of these is false
. 未指定 subpatterns 相符的順序,且失敗的比對可能不符合執行時間的所有 subpatterns。The order in which subpatterns are matched is not specified, and a failed match may not match all subpatterns at runtime. 如果比對成功,而且 property_pattern 的 simple_designation 是 single_variable_designation,它會定義指派相符值之 T 類型的變數。If the match succeeds and the simple_designation of the property_pattern is a single_variable_designation, it defines a variable of type T that is assigned the matched value.
注意:屬性模式可以用來搭配匿名型別進行模式比對。Note: The property pattern can be used to pattern-match with anonymous types.
範例Example
if (o is string { Length: 5 } s)
Switch 運算式Switch Expression
針對運算式內容,會將 switch_expression 加入至支援 switch
類似的語法。A switch_expression is added to support switch
-like semantics for an expression context.
C # 語言語法會透過下列語法的生產增強:The C# language syntax is augmented with the following syntactic productions:
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。The switch_expression is not permitted as an expression_statement.
我們即將在未來的修訂版本中放寬這項功能。We are looking at relaxing this in a future revision.
Switch_expression 的型別是在 switch_expression_arm s 的 token 右邊出現的 最常見運算式類型 =>
(如果這類型別存在),而且 switch 運算式的每個 arm 中的運算式都可以隱含地轉換成該型別。The type of the switch_expression is the best common type of the expressions appearing to the right of the =>
tokens of the switch_expression_arm s if such a type exists and the expression in every arm of the switch expression can be implicitly converted to that type. 此外,我們還新增了一個新的 switch expression 轉換,這是從 switch 運算式預先定義的隱含轉換,以及從 T
每個 arm 的運算式隱含轉換到的每個類型 T
。In addition, we add a new switch expression conversion, which is a predefined implicit conversion from a switch expression to every type T
for which there exists an implicit conversion from each arm's expression to T
.
如果部分 switch_expression_arm 的模式不會影響結果,這會是一項錯誤,因為先前的模式和防護一律會相符。It is an error if some switch_expression_arm's pattern cannot affect the result because some previous pattern and guard will always match.
如果 switch 運算式的某些 arm 處理其輸入的每個值,則會將 switch 運算式視為 詳盡 。A switch expression is said to be exhaustive if some arm of the switch expression handles every value of its input. 如果 switch 運算式不 完整,則編譯器應該會產生警告。The compiler shall produce a warning if a switch expression is not exhaustive.
在執行時間, switch_expression 的結果是第一個 switch_expression_arm 的 運算式 的值, switch_expression 左邊的運算式符合 switch_expression_arm 的模式,而 case_guard 的 switch_expression_arm (如果有的話)則會評估為 true
。At runtime, the result of the switch_expression is the value of the expression of the first switch_expression_arm for which the expression on the left-hand-side of the switch_expression matches the switch_expression_arm's pattern, and for which the case_guard of the switch_expression_arm, if present, evaluates to true
. 如果沒有這類 switch_expression_arm, switch_expression 會擲回例外狀況的實例 System.Runtime.CompilerServices.SwitchExpressionException
。If there is no such switch_expression_arm, the switch_expression throws an instance of the exception System.Runtime.CompilerServices.SwitchExpressionException
.
在元組常值上切換時的選擇性括弧Optional parens when switching on a tuple literal
若要使用 switch_statement 來切換元組常值,您必須撰寫看似多餘的內容括弧In order to switch on a tuple literal using the switch_statement, you have to write what appear to be redundant parens
switch ((a, b))
{
允許To permit
switch (a, b)
{
當要開啟的運算式是元組常值時,switch 語句的括弧是選擇性的。the parentheses of the switch statement are optional when the expression being switched on is a tuple literal.
模式比對中的評估順序Order of evaluation in pattern-matching
讓編譯器在重新排列模式比對期間所執行的作業時有彈性,可提供彈性,讓您可以用來改善模式比對的效率。Giving the compiler flexibility in reordering the operations executed during pattern-matching can permit flexibility that can be used to improve the efficiency of pattern-matching. (未強制強制) 需求是以模式存取的屬性,以及解構方法,都必須是「純」 (副作用可用、等冪等) 。The (unenforced) requirement would be that properties accessed in a pattern, and the Deconstruct methods, are required to be "pure" (side-effect free, idempotent, etc). 這並不表示我們會將純度新增為語言概念,只是為了讓編譯器在重新排序作業中有彈性。That doesn't mean that we would add purity as a language concept, only that we would allow the compiler flexibility in reordering operations.
解決方式 2018-04-04 LDM:已確認:編譯器可以重新排列對 Deconstruct
中方法的呼叫、屬性存取和調用的調用, ITuple
而且可能會假設傳回的值與多個呼叫相同。Resolution 2018-04-04 LDM: confirmed: the compiler is permitted to reorder calls to Deconstruct
, property accesses, and invocations of methods in ITuple
, and may assume that returned values are the same from multiple calls. 編譯器不應叫用無法影響結果的函式,而且在未來對編譯器產生的評估順序進行任何變更之前,我們會非常小心。The compiler should not invoke functions that cannot affect the result, and we will be very careful before making any changes to the compiler-generated order of evaluation in the future.
一些可能的優化Some Possible Optimizations
模式比對的編譯可以利用模式的通用部分。The compilation of pattern matching can take advantage of common parts of patterns. 例如,如果 switch_statement 中兩個連續模式的最上層類型測試是相同類型,則產生的程式碼可以略過第二個模式的類型測試。For example, if the top-level type test of two successive patterns in a switch_statement is the same type, the generated code can skip the type test for the second pattern.
當某些模式是整數或字串時,編譯器可以產生與舊版語言的 switch 語句所產生的相同類型程式碼。When some of the patterns are integers or strings, the compiler can generate the same kind of code it generates for a switch-statement in earlier versions of the language.
如需這類優化的詳細資訊,請參閱 [Scott 和 Ramsey (2000) ]。For more on these kinds of optimizations, see [Scott and Ramsey (2000)].