Rekurzivní porovnávání vzorů
Poznámka
Tento článek je specifikace funkce. Specifikace slouží jako návrhový dokument pro funkci. Zahrnuje navrhované změny specifikace spolu s informacemi potřebnými při návrhu a vývoji funkce. Tyto články se publikují, dokud nebudou navrhované změny specifikace finalizovány a začleněny do aktuální specifikace ECMA.
Mezi specifikací funkce a dokončenou implementací může docházet k nějakým nesrovnalostem. Tyto rozdíly jsou zachyceny v poznámkách ze schůzky o návrhu jazyka (LDM).
Další informace o procesu přijetí specifikací funkcí do jazyka C# najdete v článku o specifikacích .
Shrnutí
Rozšíření porovnávání vzorů pro jazyk C# umožňují mnoho výhod algebraických datových typů a porovnávání vzorů z funkčních jazyků, ale způsobem, který se hladce integruje s chováním základního jazyka. Prvky tohoto přístupu jsou inspirovány souvisejícími funkcemi v programovacích jazycích F# a Scala.
Podrobný návrh
Je výraz
Operátor is
je rozšířen tak, aby testoval výraz proti vzoru.
relational_expression
: is_pattern_expression
;
is_pattern_expression
: relational_expression 'is' pattern
;
Tato forma relational_expression je navíc k existujícím formulářům ve specifikaci jazyka C#. Jedná se o chybu v době kompilace, pokud relational_expression nalevo od tokenu is
neoznačuje hodnotu nebo nemá typ.
Každý identifikátor vzoru zavádí novou místní proměnnou, která je jistě přiřazena po is
operátoru true
(tj. jistě přiřazena, když je pravdivý).
Poznámka: Technicky vzato existuje nejednoznačnost mezi typem v
is-expression
a constant_pattern, přičemž obě možnosti mohou být platným výsledkem analýzy kvalifikovaného identifikátoru. Snažíme se jej svázat jako typ pro kompatibilitu s předchozími verzemi jazyka; pouze v případě, že se to nezdaří, vyřešíme ho jako výraz v jiných kontextech až k první nalezené věci (což musí být buď konstanta, nebo typ). Tato nejednoznačnost se nachází pouze na pravé straně výrazuis
.
Vzory
Vzory se používají v operátoru is_pattern, v switch_statementa v switch_expression k vyjádření tvaru dat, proti kterému se mají porovnávat příchozí data (které nazýváme vstupní hodnotu). Vzory můžou být rekurzivní, aby se části dat mohly shodovat s dílčími vzory.
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
: '_'
;
Model deklarace
declaration_pattern
: type simple_designation
;
declaration_pattern zkontroluje, zda je výraz daného typu, a přetypuje ho na tento typ, pokud se to potvrdí. To může zavést místní proměnnou pojmenovanou daným identifikátorem daného typu, pokud je označení single_variable_designation. Tato místní proměnná je rozhodně přiřazena, když je výsledek operace porovnávání vzorů true
.
Sémantika modulu runtime tohoto výrazu spočívá v tom, že testuje typ modulu runtime levého operandu relational_expression proti typu ve vzoru. Pokud se jedná o tento typ modulu runtime (nebo nějaký podtyp) a není null
, výsledek is operator
je true
.
Některé kombinace statického typu na levé straně a daného typu jsou považovány za nekompatibilní a vedou k chybám při překladu. Hodnota statického typu E
je označována jako vzorově kompatibilní s typem T
, pokud existuje převod identity, implicitní převod odkazu, převod pomocí zabalení, explicitní převod odkazu nebo rozbalení operace z E
na T
, nebo pokud je jeden z těchto typů otevřeným typem. Jedná se o chybu v době kompilace, pokud vstup typu E
není kompatibilní se vzorem s typem ve vzoru typu, se kterým se shoduje.
Vzor typů je užitečný pro běhové testování typů referenčních typů a nahrazuje idiom.
var v = expr as Type;
if (v != null) { // code using v
Trochu stručnější
if (expr is Type v) { // code using v
Je chybou, pokud je typ nulovatelným typem hodnoty.
Vzor typu lze použít k testování hodnot typů s možnou hodnotou null: hodnota typu Nullable<T>
(nebo zabalená T
) odpovídá vzoru typu T2 id
, pokud hodnota není null a typ T2
je T
, nebo nějaký základní typ nebo rozhraní T
. Například v fragmentu kódu
int? x = 3;
if (x is int v) { // code using v
Podmínka příkazu if
je true
za běhu a proměnná v
obsahuje hodnotu 3
typu int
uvnitř bloku. Po bloku je proměnná v
v oboru, ale není rozhodně přiřazena.
Konstantní vzor
constant_pattern
: constant_expression
;
Konstantní vzor testuje hodnotu výrazu proti konstantní hodnotě. Konstantou může být libovolný konstantní výraz, například literál, název deklarované proměnné const
nebo výčtová konstanta. Pokud vstupní hodnota není otevřeným typem, konstantní výraz se implicitně převede na typ odpovídajícího výrazu; Pokud typ vstupní hodnoty není vzorově kompatibilní s typem konstantního výrazu, je operace porovnávání vzorů chybou.
Vzor c se považuje za odpovídající převedenou vstupní hodnotu e, pokud object.Equals(c, e)
vrátí true
.
Očekáváme, že e is null
bude nejběžnějším způsobem, jak otestovat null
v nově napsaném kódu, protože nemůže vyvolat uživatelem definované operator==
.
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
;
Pokud je označení jednoduché označení, vzor odpovídá výrazu e. Jinými slovy, shoda se vzorem var vždy proběhne úspěšně s simple_designation. Jestliže simple_designation je single_variable_designation, je hodnota e vázána na nově vytvořenou místní proměnnou. Typ místní proměnné je statický typ e.
Je-li označení tuple_designation, je vzor ekvivalentní positional_pattern označení formuláře (var
, ... )
, kde jsou označenís nalezeny v tuple_designation. Například vzor var (x, (y, z))
je ekvivalentní (var x, (var y, var z))
.
Jedná se o chybu, pokud se název var
váže na typ.
Vyřadit vzor
discard_pattern
: '_'
;
Výraz e odpovídá vzoru _
vždy. Jinými slovy, každý výraz odpovídá vzoru vyřazení.
Vzor zahození nelze použít jako vzor is_pattern_expression.
Poziční vzor
Poziční vzor kontroluje, že vstupní hodnota není null
, vyvolá odpovídající metodu Deconstruct
a provede další porovnávání vzorů s výslednými hodnotami. Podporuje také syntaxi vzoru podobnou n-tici (bez zadaného typu) v případech, kdy je typ vstupní hodnoty stejný jako typ obsahující Deconstruct
, nebo když je typ vstupní hodnoty typem n-tice, nebo když je typ vstupní hodnoty object
nebo ITuple
a typ modulu runtime výrazu implementuje ITuple
.
positional_pattern
: type? '(' subpatterns? ')' property_subpattern? simple_designation?
;
subpatterns
: subpattern
| subpattern ',' subpatterns
;
subpattern
: pattern
| identifier ':' pattern
;
Pokud typ vynecháme, vezmeme ho jako statický typ vstupní hodnoty.
Vzhledem k porovnání vstupní hodnoty se vzorem typ(
subpattern_list)
je metoda vybrána hledáním v typu mezi přístupnými deklaracemi Deconstruct
a výběrem jedné z nich podle stejných pravidel jako pro dekonstrukční deklaraci.
Jedná se o chybu, pokud positional_pattern vynechá typ, má jediný podvzor bez identifikátoru , nemá žádný podobor a nemá žádné jednoduché označení . Toto vyjasňuje mezi constant_pattern, která je v závorkách, a positional_pattern.
Chcete-li extrahovat hodnoty, které se mají shodovat se vzory v seznamu,
- Pokud byl typ vynechán a typ vstupní hodnoty je typ n-tice, je nutné, aby počet subpatternů byl stejný jako kardinalita n-tice. Každý prvek n-tice je přiřazen k odpovídajícímu podvzoru a shoda proběhne úspěšně, pokud jsou všechny tyto shody úspěšné. Pokud má některý podpattern identifikátor , pak musí pojmenovat prvek n-tice na odpovídající pozici v typu n-tice.
- V opačném případě, pokud vhodný
Deconstruct
existuje jako člen typu, jedná se o chybu v době kompilace, pokud typ vstupní hodnoty není vzorově kompatibilní s typem. Při běhu se testuje vstupní hodnota na typ . Pokud se to nezdaří, shoda pozičního vzoru selže. Pokud se to podaří, vstupní hodnota se převede na tento typ aDeconstruct
se vyvolá s nově vytvořenými proměnnými generovanými kompilátorem pro příjem parametrůout
. Každá přijatá hodnota se porovnává s odpovídajícím dílčím vzorcem, a shoda je úspěšná, pokud se shodují všechny. Pokud má některý podpattern identifikátor , pak tento identifikátor musí pojmenovat parametr na odpovídající pozici vDeconstruct
. - Jinak pokud je typ vynechán a vstupní hodnota je typu
object
neboITuple
nebo některého typu, který lze převést naITuple
implicitním převodem odkazu, a mezi podpaterne těchto vzorců se nezobrazí žádný identifikátor , pak použijemeITuple
. - V opačném případě se jedná o chybu v době kompilace.
Pořadí, ve kterém se podsložky shodují za běhu, není specifikováno a neúspěšná shoda se nemusí pokoušet spárovat všechny podsložky.
Příklad
Tento příklad používá mnoho funkcí popsaných v této specifikaci.
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 };
Vzorec vlastnosti
Vzor vlastností kontroluje, že vstupní hodnota není null
a rekurzivně odpovídá hodnotám extrahovaným použitím přístupných vlastností nebo polí.
property_pattern
: type? property_subpattern simple_designation?
;
property_subpattern
: '{' '}'
| '{' subpatterns ','? '}'
;
Jedná se o chybu, pokud jakýkoli dílčí vzor v rámci vlastnostního vzoru neobsahuje žádný identifikátor (musí být ve druhé formě, která obsahuje identifikátor ). Koncová čárka za posledním subpatternem je volitelná.
Všimněte si, že vzor kontroly hodnoty null vychází z triviálního vzoru vlastností. Pokud chcete zkontrolovat, jestli je řetězec s
nenulový, můžete napsat libovolný z následujících formulářů.
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 {}) ...
Vzhledem k porovnání výrazu
Za běhu je výraz testován proti T. Pokud selže, shoda vzoru vlastnosti selže a výsledek je false
. Pokud je to úspěšné, pak se každé property_subpattern pole nebo vlastnost přečte a jeho hodnota se shoduje s odpovídajícím vzorem. Výsledek celého zápasu je false
pouze v případě, že výsledek některého z nich je false
. Pořadí, ve kterém se dílčí vzory shodují, není specifikováno, a pokud dojde k neúspěšné shodě, nemusí být všechny dílčí vzory odpovídající během provádění. Pokud shoda proběhne úspěšně a simple_designationproperty_pattern je single_variable_designation, definuje se proměnná typu T, které je přiřazena odpovídající hodnota.
Poznámka: Vzor vlastnosti lze použít k porovnání s anonymními typy.
Příklad
if (o is string { Length: 5 } s)
Výraz Switch
Byl přidán výraz switch_expression pro podporu sémantiky podobné switch
v kontextu výrazu.
Syntaxe jazyka C# je rozšířena o následující syntaktické produkce:
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 není povolena jako expression_statement.
Zvažujeme uvolnění tohoto opatření v rámci budoucí revize.
Typ switch_expression je nejlepším běžným typem (§12.6.3.15) výrazů, které se zobrazují napravo od =>
tokenů switch_expression_arms, pokud takový typ existuje a výraz v každé paži výrazu přepínače lze implicitně převést na tento typ. Kromě toho přidáváme nový převod výrazů, což je předdefinovaný implicitní převod z výrazů přepínače na každý typ T
, pro který existuje implicitní převod z výrazu každé větve na T
.
Jedná se o chybu, pokud určitý vzor switch_expression_armnemůže ovlivnit výsledek, protože některý předchozí vzor a ochrana budou vždy odpovídat.
Výraz přepínače je vyčerpávající, pokud některé rameno výrazu switch zpracovává každou hodnotu svého vstupu. Kompilátor vygeneruje upozornění, pokud výraz přepínače není vyčerpávající.
Při spuštění je výsledkem switch_expression hodnota výrazu prvního switch_expression_arm, pro který se výraz na levé straně switch_expression shoduje se vzorem switch_expression_arma pro který case_guardswitch_expression_arm, pokud je k dispozici, vyhodnotí jako true
. Pokud taková switch_expression_armneexistuje, switch_expression vyvolá instanci výjimky System.Runtime.CompilerServices.SwitchExpressionException
.
Volitelné závorky při použití literálu n-tice
Pokud chcete přepnout literál řazené kolekce členů pomocí switch_statement, musíte napsat, co se zdá být redundantní parens.
switch ((a, b))
{
Povolení
switch (a, b)
{
Závorky u příkazu switch jsou volitelné, když je výraz, na který se přepíná, literál n-tice.
Pořadí vyhodnocení v porovnávání vzorů
Díky flexibilitě kompilátoru při přeuspořádání operací provedených během porovnávání vzorů může být povolena flexibilita, která se dá použít ke zlepšení efektivity porovnávání vzorů. Požadavek (nevymahatelné) by byl takový, že vlastnosti, ke kterým se přistupuje ve vzoru, a dekonstrukční metody musí být "čisté" (bez vedlejšího efektu, idempotentní atd.). To neznamená, že bychom přidali čistotu jako koncept jazyka, pouze to, že bychom umožnili flexibilitu kompilátoru při přeuspořádání operací.
usnesení 2018-04-04 LDM: potvrzeno: kompilátor může změnit pořadí volání Deconstruct
, přístupů k vlastnostem a vyvolání metod v ITuple
a může předpokládat, že hodnoty vrácené z více volání jsou stejné. Kompilátor by neměl vyvolat funkce, které nemohou ovlivnit výsledek, a před provedením jakýchkoli změn v pořadí vyhodnocení vygenerovaném kompilátorem v budoucnu budeme velmi opatrní.
Některé možné optimalizace
Kompilace porovnávání vzorů může využívat běžné části vzorů. Pokud je například test typu nejvyšší úrovně dvou následných vzorů ve switch_statement stejného typu, vygenerovaný kód může přeskočit test typu pro druhý vzor.
Pokud jsou některé vzory celé číslo nebo řetězce, kompilátor může vygenerovat stejný druh kódu, který generuje pro příkaz switch-statement v dřívějších verzích jazyka.
Další informace o těchto typech optimalizací najdete v tématu [Scott a Ramsey (2000)].
C# feature specifications