Recursieve patroonherkenning
Notitie
Dit artikel is een functiespecificatie. De specificatie fungeert als het ontwerpdocument voor de functie. Het bevat voorgestelde specificatiewijzigingen, samen met informatie die nodig is tijdens het ontwerp en de ontwikkeling van de functie. Deze artikelen worden gepubliceerd totdat de voorgestelde specificaties zijn voltooid en opgenomen in de huidige ECMA-specificatie.
Er kunnen enkele verschillen zijn tussen de functiespecificatie en de voltooide implementatie. Deze verschillen worden vastgelegd in de relevante LDM-notities (Language Design Meeting).
Meer informatie over het proces voor het aannemen van functiespeclets in de C#-taalstandaard vindt u in het artikel over de specificaties.
Probleem met kampioen: https://github.com/dotnet/csharplang/issues/45
Samenvatting
Patroonkoppelingsextensies voor C# maken veel van de voordelen mogelijk van algebraïsche gegevenstypen en patroonkoppeling van functionele talen, maar op een manier die soepel kan worden geïntegreerd met het gevoel van de onderliggende taal. Elementen van deze benadering zijn geïnspireerd door gerelateerde functies in de programmeertalen F# en Scala.
Gedetailleerd ontwerp
Is expressie
De operator is
wordt uitgebreid om een expressie te testen op basis van een patroon.
relational_expression
: is_pattern_expression
;
is_pattern_expression
: relational_expression 'is' pattern
;
Deze vorm van relational_expression is een aanvulling op de bestaande vormen in de C#-specificatie. Het is een compilatiefout als de relational_expression links van het is
token geen waarde aanwijst of geen type heeft.
Elke identifier van het patroon introduceert een nieuwe lokale variabele die definitief toegewezen is nadat de operator is
is true
(dat wil zeggen definitief toegewezen wanneer deze waar is).
Opmerking: er is technisch gezien een dubbelzinnigheid tussen type in een
is-expression
en constant_pattern, die beide een geldige parsering van een gekwalificeerde id kunnen zijn. We proberen het te binden als een type voor compatibiliteit met eerdere versies van de taal; alleen als dat mislukt, lossen we het op omdat we een expressie in andere contexten uitvoeren, tot het eerste wat wordt gevonden (wat een constante of een type moet zijn). Deze dubbelzinnigheid is alleen aan de rechterkant van eenis
expressie aanwezig.
Patronen
Patronen worden gebruikt in de operator is_pattern, in een switch_statementen in een switch_expression om de vorm van gegevens uit te drukken waarmee binnenkomende gegevens (die we de invoerwaarde noemen) moeten worden vergeleken. Patronen kunnen recursief zijn, zodat delen van de gegevens kunnen worden vergeleken met subpatronen.
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
: '_'
;
Declaratiepatroon
declaration_pattern
: type simple_designation
;
De declaration_pattern test zowel of een expressie van een bepaald type is als het naar dat type casten als de test slaagt. Dit kan een lokale variabele van het opgegeven type invoeren met de opgegeven identificator, als de aanduiding een single_variable_designationis. Deze lokale variabele wordt zeker toegewezen wanneer het resultaat van de patroonvergelijkingsbewerking true
is.
De runtime-semantische van deze expressie is dat het runtimetype van de linker-relational_expression operand wordt getest op het type in het patroon. Als het van dat runtimetype (of een subtype) is en het is niet null
, dan is het resultaat van de is operator
true
.
Bepaalde combinaties van statisch type van de linkerkant en het opgegeven type worden beschouwd als niet compatibel en resulteren in compilatietijdfout. Een waarde van het statische type E
wordt geacht patroon-compatibel te zijn met een type T
als er een identiteitsconversie, een impliciete verwijzingsconversie, een boxing-conversie, een expliciete verwijzingsconversie of een unboxing-conversie bestaat van E
naar T
, of als een van deze typen een open type is. Het is een compilatiefout als een invoer van het type E
niet patrooncompatibel is met het type in een typepatroon waarmee het overeenkomt.
Het typepatroon is handig voor het uitvoeren van runtime-typetests van referentietypen en vervangt het idiom
var v = expr as Type;
if (v != null) { // code using v
Met de iets beknoptere versie
if (expr is Type v) { // code using v
Dit is een fout als type een null-waardetype is.
Het typepatroon kan worden gebruikt om waarden van null-typen te testen: een waarde van het type Nullable<T>
(of een ingepakte T
) komt overeen met een typepatroon T2 id
als de waarde niet null is en het type van T2
T
is, of een basistype of interface van T
. Bijvoorbeeld in het codefragment
int? x = 3;
if (x is int v) { // code using v
De voorwaarde van de if
-instructie is true
tijdens de uitvoering en de variabele v
bevat de waarde 3
van het type int
binnen het blok. Na het blok is de variabele v
in scope, maar niet gegarandeerd toegewezen.
Constant patroon
constant_pattern
: constant_expression
;
Met een constant patroon wordt de waarde van een expressie getest op basis van een constante waarde. De constante kan elke constante expressie zijn, zoals een letterlijke expressie, de naam van een gedeclareerde const
variabele of een opsommingsconstante. Wanneer de invoerwaarde geen open type is, wordt de constante uitdrukking impliciet geconverteerd naar het type van de overeenkomende uitdrukking; als het type van de invoerwaarde niet patrooncompatibel is met het type van de constante uitdrukking, resulteert de patroonvergelijkingsbewerking in een fout.
Het patroon c wordt beschouwd als overeenkomend met de geconverteerde invoerwaarde e als object.Equals(c, e)
true
zou retourneren.
We verwachten dat e is null
de meest voorkomende manier zal zijn om te testen op null
in nieuw geschreven code, omdat er geen door de gebruiker gedefinieerde operator==
kan worden aangeroepen.
Var-patroon
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
;
Als de aanduiding een simple_designationis, komt een expressie e overeen met het patroon. Met andere woorden, een overeenkomst met een var-patroon slaagt altijd met een simple_designation. Als de simple_designation een single_variable_designationis, is de waarde van e gebonden aan een nieuw geïntroduceerde lokale variabele. Het type van de lokale variabele is het statische type e.
Als de aanduiding een tuple_designationis, is het patroon gelijk aan een positional_pattern van de vorm (var
aanduiding, ... )
waar de aanduidings zijn gevonden in de tuple_designation. Het patroon var (x, (y, z))
is bijvoorbeeld gelijk aan (var x, (var y, var z))
.
Dit is een fout als de naam var
wordt gekoppeld aan een type.
Patroon weggooien
discard_pattern
: '_'
;
Een expressie e komt altijd overeen met het patroon _
. Met andere woorden, elke expressie komt overeen met het verwijderingspatroon.
Een verwijderpatroon mag niet worden gebruikt als patroon van een is_pattern_expression.
Positioneel patroon
Een positioneel patroon controleert of de invoerwaarde niet is null
, roept een geschikte Deconstruct
methode aan en voert verdere patroonkoppeling uit op de resulterende waarden. Het ondersteunt ook een tuple-achtige patroonsyntaxis (zonder het type dat wordt opgegeven) wanneer het type van de invoerwaarde hetzelfde is als het type dat Deconstruct
bevat, of als het type van de invoerwaarde een tupletype is, of als het type van de invoerwaarde object
of ITuple
is en het runtimetype van de expressie ITuple
implementeert.
positional_pattern
: type? '(' subpatterns? ')' property_subpattern? simple_designation?
;
subpatterns
: subpattern
| subpattern ',' subpatterns
;
subpattern
: pattern
| identifier ':' pattern
;
Als het type wordt weggelaten, beschouwen we het als het statische type van de invoerwaarde.
Gezien een overeenkomst van een invoerwaarde met het patroon type(
subpattern_list)
, wordt een methode geselecteerd door te zoeken in type voor toegankelijke declaraties van Deconstruct
en er een te selecteren met dezelfde regels als voor de declaratie voor de deconstructie.
Het is een fout als een positional_pattern het type weglaat, één subpatroon zonder een id, geen property_subpattern heeft en geen simple_designationheeft. Dit maakt onderscheid tussen een constant_pattern dat tussen haakjes staat en een positional_pattern.
Als u de waarden wilt extraheren die overeenkomen met de patronen in de lijst,
- Als type is weggelaten en het type invoerwaarde een tupletype is, moet het aantal subpatronen hetzelfde zijn als de kardinaliteit van de tuple. Ieder element van de tuple wordt vergeleken met het bijbehorende subpatroon, en de overeenkomst slaagt als al deze overeenkomsten slagen. Als een subpatroon een idheeft, moet dat een tuple-element een naam geven op de bijbehorende positie in het tuple-type.
- Anders, als er een geschikte
Deconstruct
bestaat als lid van type, resulteert het in een compilatiefout wanneer het type van de invoerwaarde niet patrooncompatibel is met type. Tijdens runtime wordt de invoerwaarde getest op basis van type. Als dit mislukt, mislukt de positionele patroonovereenkomst. Als dit lukt, wordt de invoerwaarde geconverteerd naar dit type enDeconstruct
wordt aangeroepen met nieuwe door compiler gegenereerde variabelen om deout
parameters te ontvangen. Elke waarde die is ontvangen, wordt vergeleken met het overeenkomstige subpatroon , en de overeenkomst slaagt als ze allemaal slagen. Als een subpatroon een idheeft, moet dat de naam zijn van een parameter op de overeenkomstige positie vanDeconstruct
. - Als type is weggelaten en de invoerwaarde van het type
object
ofITuple
of een type is dat kan worden geconverteerd naarITuple
door een impliciete verwijzingsconversie, en er geen identificator wordt weergegeven onder de subpatronen, gebruiken weITuple
voor de overeenkomst. - Anders is het patroon een compilatiefout.
De volgorde waarin subpatronen tijdens runtime worden vergeleken, is niet opgegeven en een mislukte overeenkomst probeert mogelijk niet alle subpatronen te vinden.
Voorbeeld
In dit voorbeeld worden veel van de functies gebruikt die in deze specificatie worden beschreven
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 };
Eigenschapspatroon
Een eigenschapspatroon controleert of de invoerwaarde niet null
en recursief overeenkomt met waarden die zijn geëxtraheerd door het gebruik van toegankelijke eigenschappen of velden.
property_pattern
: type? property_subpattern simple_designation?
;
property_subpattern
: '{' '}'
| '{' subpatterns ','? '}'
;
Het is een fout als een subpatroon van een property_pattern geen id bevat (dit moet van het tweede formulier zijn, dat een idheeft). Een volgkomma na het laatste subpatroon is optioneel.
Merk op dat een null-controlepatroon voortvloeit uit een triviaal eigenschapspatroon. Als u wilt controleren of de tekenreeks s
niet null is, kunt u een van de volgende formulieren schrijven
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 {}) ...
Gezien de overeenkomst van een expressie e met het patroon type{
property_pattern_list}
, is het een compilatiefout als de expressie e niet patrooncompatibel is met het type T dat wordt aangeduid door type. Als het type afwezig is, wordt dit het statische type e. Als de -id aanwezig is, wordt een patroonvariabele van het type en typegedeclareerd. Elk van de id's die aan de linkerkant van de property_pattern_list worden weergegeven, moet een toegankelijk leesbare eigenschap of veld van T-aanwijzen. Als de simple_designation van de property_pattern aanwezig is, wordt een patroonvariabele van het type T-gedefinieerd.
Tijdens runtime wordt de expressie getest op basis van T-. Als dit mislukt, mislukt de overeenkomst met het eigenschapspatroon en wordt het resultaat false
. Als dit lukt, wordt elk property_subpattern veld of eigenschap gelezen en wordt de bijbehorende waarde vergeleken met het bijbehorende patroon. Het resultaat van de hele wedstrijd is false
alleen als het resultaat van een van deze false
is. De volgorde waarin subpatronen worden vergeleken, wordt niet opgegeven en een mislukte overeenkomst komt mogelijk niet overeen met alle subpatronen tijdens runtime. Als de overeenkomst slaagt en de simple_designation van de property_pattern een single_variable_designationis, definieert deze een variabele van het type T waaraan de overeenkomende waarde is toegewezen.
Opmerking: Het eigenschapspatroon kan worden gebruikt om patroonovereenkomsten met anonieme typen te gebruiken.
Voorbeeld
if (o is string { Length: 5 } s)
Schakelexpressie
Er wordt een switch_expression toegevoegd ter ondersteuning van switch
-achtige semantiek voor een expressiecontext.
De syntaxis van de C#-taal wordt uitgebreid met de volgende syntactische producties:
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
;
De schakeluitdrukking is niet toegestaan als een expressieverklaring.
We kijken erop om dit in een toekomstige herziening te versoepelen.
Het type van de switchexpressie is het meest voorkomende algemene type (§12.6.3.15) van de expressies die rechts van de =>
tokens van de switchexpressie-armverschijnen, als een dergelijk type bestaat en de expressie in elke arm van de switchexpressie impliciet kan worden geconverteerd naar dat type. Daarnaast voegen we een nieuwe expressieconversie toe, een vooraf gedefinieerde impliciete conversie van een switchexpressie naar elk type T
waarvoor er een impliciete conversie van elke arm-expressie tot T
bestaat.
Het is een fout als het patroon van een bepaalde switch_expression_armhet resultaat niet kan beïnvloeden, omdat een eerder patroon en bijbehorende voorwaarde altijd zullen overeenkomen.
Een switchexpressie wordt uitputtend genoemd als een tak van de switchexpressie elke waarde van de invoer verwerkt. De compiler produceert een waarschuwing als een switchexpressie niet volledigeis.
Tijdens de uitvoeringstijd is het resultaat van de switch_expression de waarde van de switch_expression van de eerste switch_expression_arm waarvoor de expressie aan de linkerkant van de switch_expression overeenkomt met het patroon van de switch_expression_arm, en waarvoor, indien aanwezig, de case_guard van de switch_expression_armevalueert naar true
. Als er geen dergelijke switch_expression_armis, genereert de switch_expression een exemplaar van de uitzondering System.Runtime.CompilerServices.SwitchExpressionException
.
Optionele haakjes bij het schakelen op een tupleliteral
Als u een letterlijke tuple wilt inschakelen met behulp van de switch_statement, moet u schrijven wat redundante parens lijken te zijn
switch ((a, b))
{
Toestaan
switch (a, b)
{
de haakjes van de switch-instructie zijn optioneel wanneer de expressie waarop wordt geswitcht een letterlijke tuple is.
Volgorde van evaluatie in patroonkoppeling
Dankzij de flexibiliteit van de compiler bij het opnieuw ordenen van de bewerkingen die worden uitgevoerd tijdens patroonkoppeling, kan flexibiliteit worden gebruikt om de efficiëntie van patroonkoppeling te verbeteren. De (niet afgedwongen) vereiste zou zijn dat eigenschappen die in een patroon worden benaderd, en de deconstructiemethoden, 'puur' moeten zijn (neveneffectvrij, idempotent, enzovoort). Dat betekent niet dat we zuiverheid als taalconcept zouden toevoegen, alleen dat we de compiler flexibiliteit zouden bieden bij het opnieuw ordenen van bewerkingen.
resolutie 2018-04-04 LDM: bevestigd: de compiler mag aanroepen naar Deconstruct
, eigenschapstoegang en aanroepen van methoden in ITuple
opnieuw ordenen en kan aannemen dat geretourneerde waarden hetzelfde zijn van meerdere aanroepen. De compiler mag geen functies aanroepen die geen invloed kunnen hebben op het resultaat en we zijn zeer voorzichtig voordat we wijzigingen aanbrengen in de door de compiler gegenereerde volgorde van evaluatie in de toekomst.
Enkele mogelijke optimalisaties
De compilatie van patroonherkenning kan profiteren van veelvoorkomende onderdelen van patronen. Als de test op het hoogste niveau van twee opeenvolgende patronen in een switch_statement bijvoorbeeld hetzelfde type is, kan de gegenereerde code de typetest voor het tweede patroon overslaan.
Wanneer sommige patronen gehele getallen of tekenreeksen zijn, kan de compiler hetzelfde type code genereren dat wordt gegenereerd voor een switch-instructie in eerdere versies van de taal.
C# feature specifications