Rekursiv mönstermatchning
Notera
Den här artikeln är en funktionsspecifikation. Specifikationen fungerar som designdokument för funktionen. Den innehåller föreslagna specifikationsändringar, tillsammans med information som behövs under utformningen och utvecklingen av funktionen. Dessa artiklar publiceras tills de föreslagna specifikationsändringarna har slutförts och införlivats i den aktuella ECMA-specifikationen.
Det kan finnas vissa skillnader mellan funktionsspecifikationen och den slutförda implementeringen. Dessa skillnader samlas in i de relevanta LDM-anteckningarna (Language Design Meeting).
Du kan läsa mer om processen för att införa funktionsspecifikationer i C#-språkstandarden i artikeln om specifikationerna.
Championfråga: https://github.com/dotnet/csharplang/issues/45
Sammanfattning
Mönstermatchningstillägg för C# möjliggör många av fördelarna med algebraiska datatyper och mönstermatchning från funktionella språk, men på ett sätt som smidigt integreras med känslan av det underliggande språket. Element i den här metoden är inspirerade av relaterade funktioner i programmeringsspråken F# och Scala.
Detaljerad design
Är uttryck
Operatorn is
utökas för att testa ett uttryck mot ett mönster.
relational_expression
: is_pattern_expression
;
is_pattern_expression
: relational_expression 'is' pattern
;
Den här typen av relational_expression är utöver de befintliga formulären i C#-specifikationen. Det är ett kompileringsfel om relational_expression till vänster om is
token inte anger något värde eller inte har någon typ.
Varje identifierare i mönstret introducerar en ny lokal variabel som är definitivt tilldelad efter att is
-operatorn har true
(dvs. definitivt tilldelad när värdet är sant).
Obs! Det finns tekniskt sett en tvetydighet mellan typ i en
is-expression
och constant_pattern, vilket kan vara en giltig pars av en kvalificerad identifierare. Vi försöker binda den som en typ för kompatibilitet med tidigare versioner av språket. endast om det misslyckas löser vi det som vi gör ett uttryck i andra kontexter, till det första som hittas (som måste vara antingen en konstant eller en typ). Den här tvetydigheten finns bara på högra sidan av ettis
-uttryck.
Mönster
Mönster används i operatorn is_pattern, i en switch_statementoch i en switch_expression för att representera datans struktur, mot vilket inkommande data (som vi kallar indata) ska jämföras. Mönster kan vara rekursiva så att delar av data kan matchas mot undermönster.
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
: '_'
;
Deklarationsmönster
declaration_pattern
: type simple_designation
;
declaration_pattern testar att ett uttryck är av en specificerad typ och gör det till den typen om testet lyckas. Detta kan introducera en lokal variabel av den angivna typen med namnet av den angivna identifieraren, om beteckningen är en single_variable_designation. Den lokala variabeln är definitivt tilldelad när resultatet av mönstermatchningsåtgärden är true
.
Körtidssemantiken för det här uttrycket är att den testar körtidstypen för den vänstra relational_expression operandens typ mot typen i mönstret. Om det är av den exekveringstypen (eller någon undertyp) och inte är null
, är resultatet av is operator
true
.
Vissa kombinationer av statisk typ av vänster sida och den angivna typen anses vara inkompatibla och resulterar i kompileringsfel. Ett värde av statisk typ E
sägs vara mönsterkompatibel med en typ T
om det finns en identitetskonvertering, en implicit referenskonvertering, en boxningskonvertering, en explicit referenskonvertering eller en avboxningskonvertering från E
till T
eller om någon av dessa typer är en öppen typ. Det är ett kompileringsfel om indata av typen E
inte är mönsterkompatibla med typ i ett typmönster som det matchas med.
Typmönstret är användbart för att utföra körningstyptester av referenstyper och ersätter formspråket
var v = expr as Type;
if (v != null) { // code using v
Med den något mer koncisa texten
if (expr is Type v) { // code using v
Det är ett fel om typ är en nullbar värdetyp.
Typmönstret kan användas för att testa värden av null-typer: ett värde av typen Nullable<T>
(eller en rutad T
) matchar ett typmönster T2 id
om värdet inte är null och typen av T2
är T
eller någon bastyp eller gränssnitt för T
. Till exempel i kodfragmentet
int? x = 3;
if (x is int v) { // code using v
Villkoret för if
-instruktionen är true
vid körning och variabeln v
innehåller värdet 3
av typen int
inom blocket. Efter blocket är variabeln v
i scope men inte garanterat tilldelad.
Konstant mönster
constant_pattern
: constant_expression
;
Ett konstant mönster testar värdet för ett uttryck mot ett konstant värde. Konstanten kan vara ett konstant uttryck, till exempel en literal, namnet på en deklarerad const
variabel eller en uppräkningskonstant. När indatavärdet inte är en öppen typ konverteras konstantuttrycket implicit till typen för det matchade uttrycket. Om typen av indatavärde inte är mönsterkompatibel med typen av konstant uttryck är mönstermatchningsåtgärden ett fel.
Mönstret c anses matcha det konverterade indatavärdet e om object.Equals(c, e)
skulle returnera true
.
Vi förväntar oss att se e is null
som det vanligaste sättet att testa för null
i nyskriven kod, eftersom det inte kan anropa en användardefinierad operator==
.
Var-mönster
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
;
Om -beteckningen är en simple_designationmatchar ett uttryck e mönstret. Med andra ord lyckas en matchning till ett var-mönster alltid med en enkel_beteckning. Om simple_designation är en single_variable_designation, är värdet för e bundet till en nyligen introducerad lokal variabel. Typen av lokal variabel är den statiska typen av e.
Om beteckning är en tuple_designation, motsvarar mönstret ett mönster av typen positional_pattern av formuläret (var
beteckning, ... )
där beteckningarnaär de som finns inom tuple_designation. Mönstret var (x, (y, z))
motsvarar till exempel (var x, (var y, var z))
.
Det är ett fel om namnet var
binder till en typ.
Ignorera mönster
discard_pattern
: '_'
;
Ett uttryck e matchar alltid mönstret _
. Med andra ord matchar varje uttryck ignoreringsmönstret.
Ett ignorerandemönster får inte användas som mönster för en is_pattern_expression.
Positionsmönster
Ett positionsmönster kontrollerar att indatavärdet inte är null
, anropar en lämplig Deconstruct
metod och utför ytterligare mönstermatchning på de resulterande värdena. Den stöder också en tupplarliknande mönstersyntax (utan att typen anges) när typen av indatavärde är samma som den typ som innehåller Deconstruct
, eller om typen av indatavärde är en tupplartyp, eller om typen av indatavärde är object
eller ITuple
och körningstypen för uttrycket implementerar ITuple
.
positional_pattern
: type? '(' subpatterns? ')' property_subpattern? simple_designation?
;
subpatterns
: subpattern
| subpattern ',' subpatterns
;
subpattern
: pattern
| identifier ':' pattern
;
Om den typen utelämnas tar vi det som den statiska typen av indatavärde.
Med tanke på en matchning av ett indatavärde med mönstret typ(
subpattern_list)
, väljs en metod genom att söka i typ efter tillgängliga deklarationer av Deconstruct
och välja en av dem med samma regler som för dekonstruktionsdeklarationen.
Det är ett fel om en positional_pattern utelämnar typen, har en enda subpattern utan en identifierare, inte har någon property_subpattern och har ingen simple_designation. Detta skiljer sig mellan en constant_pattern som är parentesiserad och en positional_pattern.
För att extrahera de värden som ska matchas mot mönstren i listan,
- Om typ utelämnades och indatavärdets typ är en tuppelns typ måste antalet undermönster vara detsamma som tuppelns kardinalitet. Varje tuppelelement matchas mot motsvarande delmönster, och matchningen lyckas om alla lyckas. Om något delmönster har en identifierare, då måste det namnge ett tupleelement på motsvarande position i tupletypen.
- Om det annars finns en lämplig
Deconstruct
som medlem av typär det ett kompileringsfel om typen av indatavärde inte är mönsterkompatibel med typ. Vid körningen testas indatavärdet mot typ. Om detta misslyckas misslyckas positionsmönstermatchningen. Om det lyckas konverteras indatavärdet till den här typen ochDeconstruct
anropas med nya kompilatorgenererade variabler för att ta emot parametrarna förout
. Varje värde som mottogs matchas mot motsvarande delmönster, och matchningen lyckas om alla dem lyckas. Om något delmönster har en identifierare, måste det namnge en parameter på motsvarande position iDeconstruct
. - Annars, om typen utelämnades och indatavärdet är av typen
object
ellerITuple
eller någon typ som kan konverteras tillITuple
genom en implicit referenskonvertering, och ingen identifierare visas bland submönstren, matchar vi genomITuple
. - Annars är mönstret ett kompileringsfel.
Ordningen i vilken undermönster matchas vid körning är ospecificerad, och en misslyckad matchning behöver inte nödvändigtvis försöka matcha alla undermönster.
Exempel
I det här exemplet används många av de funktioner som beskrivs i den här specifikationen
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 };
Egenskapsmönster
Ett egenskapsmönster kontrollerar att indatavärdet inte är null
och matchar rekursivt värden som extraheras med hjälp av tillgängliga egenskaper eller fält.
property_pattern
: type? property_subpattern simple_designation?
;
property_subpattern
: '{' '}'
| '{' subpatterns ','? '}'
;
Det är ett fel om något delmönster av ett property_pattern inte innehåller en identifierare (det måste vara i den andra formen, som har en identifierare). Ett avslutande kommatecken efter det sista underavsnittet är valfritt.
Observera att ett null-kontrollmönster faller ur ett trivialt egenskapsmönster. Om du vill kontrollera om strängen s
inte är null kan du skriva något av följande formulär
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 {}) ...
Givet en matchning av ett uttryck e till mönstret typ{
property_pattern_list}
är det ett fel vid kompileringstiden om uttrycket e inte är mönsterkompatibelt med typen T som anges av typ. Om typen saknas tar vi det som den statiska typen av e. Om identifierare finns deklarerar den en mönstervariabel av typen typ. Var och en av identifierarna som visas till vänster i dess property_pattern_list måste ange en tillgänglig läsbar egenskap eller ett fält för T-. Om simple_designation för property_pattern finns definierar den en mönstervariabel av typen T.
Under körning testas uttrycket mot T. Om detta misslyckas så misslyckas även egenskapsmönstermatchningen och resultatet är false
. Om det lyckas läss varje property_subpattern fält eller egenskap och dess värde matchas mot motsvarande mönster. Resultatet av hela matchningen är bara false
om resultatet av något av dessa är false
. Ordningen i vilken undermönster matchas specificeras inte, och en misslyckad matchning kan leda till att inte alla undermönster matchas vid körning. Om matchningen lyckas och simple_designation av property_pattern är en single_variable_designation, definieras en variabel av typen T som tilldelas det matchade värdet.
Obs! Egenskapsmönstret kan användas för att mönstermatcha med anonyma typer.
Exempel
if (o is string { Length: 5 } s)
Växla uttryck
En switch_expression läggs till för att stödja switch
-liknande semantik i en uttryckskontext.
C#-språksyntaxen utökas med följande syntaktiska produktioner:
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 tillåts inte som en expression_statement.
Vi tittar på att lätta på detta i en framtida revision.
Typen av switch_expression är den vanligaste typen (§12.6.3.15) av uttrycken som visas till höger om =>
token för switch_expression_arms om en sådan typ finns och uttrycket i varje arm i switchuttrycket implicit kan konverteras till den typen. Dessutom lägger vi till en ny växla uttryckskonvertering, som är en fördefinierad implicit konvertering från ett switch-uttryck till varje typ T
där det finns en implicit konvertering från varje arm-uttryck till T
.
Det är ett fel om vissa switch_expression_armmönster inte kan påverka resultatet eftersom något tidigare mönster och skydd alltid matchar.
Ett växeluttryck sägs vara uttömmande om någon del av växeluttrycket hanterar varje värde i indata. Kompilatorn ska utfärda en varning om ett växeluttryck inte är uttömmande.
Vid körning är resultatet av switch_expression värdet för det uttrycket för den första switch_expression_arm där uttrycket till vänster i switch_expression matchar mönstret switch_expression_armoch för vilket case_guard av switch_expression_arm, om det finns utvärderas till true
. Om det inte finns någon sådan switch_expression_armgenererar switch_expression en instans av undantaget System.Runtime.CompilerServices.SwitchExpressionException
.
Valfria parens vid växling på en tuppelliteral
För att kunna växla till en tuppelliteral med hjälp av switch_statementmåste du skriva vad som verkar vara redundanta parens
switch ((a, b))
{
Att tillåta
switch (a, b)
{
parenteserna för switch-instruktionen är valfria när det uttryck som byts ut är en tuppelliteral.
Utvärderingsordning i mönstermatchning
Om du ger kompilatorn flexibilitet när det gäller att ordna om de åtgärder som körs under mönstermatchning kan du tillåta flexibilitet som kan användas för att förbättra effektiviteten i mönstermatchning. Kravet (oforcerat) skulle vara att egenskaper som används i ett mönster och dekonstruktionsmetoderna måste vara "rena" (sidoeffektfria, idempotenta, osv.). Det betyder inte att vi skulle lägga till renhet som ett språkkoncept, bara att vi skulle tillåta kompilatorns flexibilitet vid omordningsåtgärder.
Resolution 2018-04-04 LDM: bekräftad: kompilatorn får ordna om anrop till Deconstruct
, egenskapsåtkomster och anrop av metoder i ITuple
och kan anta att returnerade värden är samma från flera anrop. Kompilatorn bör inte anropa funktioner som inte kan påverka resultatet, och vi kommer att vara mycket försiktiga innan vi gör några ändringar i den kompilatorgenererade utvärderingsordningen i framtiden.
Några möjliga optimeringar
Kompilering av mönstermatchning kan dra nytta av vanliga delar av mönster. Om till exempel toppnivåtyptestet av två efterföljande mönster i en switch_statement är av samma typ kan den genererade koden hoppa över typtestet för det andra mönstret.
När vissa av mönstren är heltal eller strängar kan kompilatorn generera samma typ av kod som den genererar för en switch-instruktion i tidigare versioner av språket.
Mer information om den här typen av optimeringar finns i [Scott och Ramsey (2000)].
C# feature specifications