Sdílet prostřednictvím


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ýrazu is.

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 a Deconstruct 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 v Deconstruct.
  • Jinak pokud je typ vynechán a vstupní hodnota je typu object nebo ITuple nebo některého typu, který lze převést na ITuple implicitním převodem odkazu, a mezi podpaterne těchto vzorců se nezobrazí žádný identifikátor , pak použijeme ITuple.
  • 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 e se vzorem typproperty_pattern_list, jedná se o chybu v době kompilace, pokud výraz e není vzorově kompatibilní s typem T určenýmtypu . Pokud typ chybí, vezmeme jej jako statický typ e. Pokud existuje identifikátor , deklaruje proměnnou vzoru typu typu. Každý identifikátor zobrazený na levé straně jeho property_pattern_list musí určovat přístupnou a čitelnou vlastnost nebo pole T. Pokud je přítomný simple_designationproperty_pattern, definuje proměnnou vzoru typu T.

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é switchv 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 ITuplea 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)].