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適用於類型T。T 如果程式中出現模式以符合類型的P模式輸入值(T),則為編譯時期錯誤,如果P不適用於 T。
範例:下列範例會產生編譯時間錯誤,因為的編譯時間類型
v為TextReader。 型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 }不過,下列不會產生編譯時間錯誤,因為的編譯時間類型
v為object。 類型的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.1) e,如果 simple_designation 是 discard_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會保留 區塊內的 型別3值int。 在區塊之後,變數v在作用域內,但未明確指派。 end 範例
11.2.3 常數模式
constant_pattern是用來測試模式輸入值(~11.1)與指定常數值的值。
constant_pattern
: constant_expression
;
如果 常數表達式從的常數表達式隱含轉換成 型別,則常數模式P適用於T 類型P。T
對於常數模式 P,其 轉換的值 為
- 如果模式輸入值的型別是整數類型或列舉類型,則模式的常數值會轉換成該類型;否則
- 如果模式輸入值的型別是整數型別或列舉類型的可為 Null 版本,則模式的常數值會轉換成其基礎類型;否則
- 模式常數值的值。
假設模式輸入值 e 和具有已轉換值 P 的常數模式,
- 如果 e 具有整數型別或列舉型別,或是其中一種可為 Null 的形式,且 v 具有整數類型,則模式會比對表達式
P的結果為 ,則比e == v對 etrue;否則為 - 如果 傳回 ,則模式
P會比對 e 值。object.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.1) e,如果指定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的類型相同時,它也支援類似元組的模式語法(不提供類型),或者如果輸入值的類型是元組類型,或者如果輸入值的類型是 object 或 System.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_pattern 和 positional_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 60end 範例
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_pattern的simple_designation,則會宣告類型為 T 的型樣變數。
在執行階段,會針對 T 測試運算式。如果失敗,則內容型樣比對會失敗,結果為 false。 如果成功,則會讀取每個 property_subpattern 欄位或屬性,並將其值與其對應的模式進行比對。 只有當其中任何一個的結果為 false時,整個比賽的結果才算false。 未指定子型樣比對的順序,且失敗的比對可能不會在執行時期測試所有子型樣。 如果比對成功,且property_pattern的simple_designation是single_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 abcend 範例
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對常數P。K
如果下列任一條件保留,一組模式會Q子化模式P:
-
P是常數模式,而且集合Q中的任何模式都會符合P已 轉換的值 -
P是 var 模式,而且模式集合Q對於模式輸入值的類型而言是詳盡的 (~11.4),而且模式輸入值不是可為 Null 的類型,或 中的Q某些模式會相符null。 -
P是具有類型的T宣告模式,而且一組模式Q對於類型而言是T的({11.4)。
11.4 模式詳盡
在非 Null 的每個可能值中,如果集合中的某些模式適用,則一組模式對於類型而言是詳盡的。 下列規則會定義類型的一組模式 何時詳盡 :
如果下列任一條件保留,一組模式 Q 會 詳盡說明 類型 T :
-
T是整數或列舉型別,或其中一個可為 Null 的版本,而且對於 's non-nullable 基礎型別的每個可能值T而言,中的Q某些模式會符合該值;或 - 中的
Q某些模式為 var 模式;或 - 中的
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 範例