Nota
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare ad accedere o modificare le directory.
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare a modificare le directory.
Nota
Questo articolo è una specifica di funzionalità. La specifica funge da documento di progettazione per la funzionalità. Include le modifiche specifiche proposte, insieme alle informazioni necessarie durante la progettazione e lo sviluppo della funzionalità. Questi articoli vengono pubblicati fino a quando le modifiche specifiche proposte non vengono completate e incorporate nella specifica ECMA corrente.
Potrebbero verificarsi alcune discrepanze tra la specifica di funzionalità e l'implementazione completata. Tali differenze vengono acquisite nelle note
Puoi saperne di più sul processo di adozione degli speclets di funzionalità nel linguaggio standard C# nell'articolo sulle specifiche di .
Problema del campione: https://github.com/dotnet/csharplang/issues/45
Sommario
Le estensioni per il pattern matching in C# consentono molti dei vantaggi dei tipi di dati algebrici e del pattern matching dai linguaggi funzionali, ma in modo da integrarsi senza problemi con la natura del linguaggio sottostante. Gli elementi di questo approccio sono ispirati alle funzionalità correlate nei linguaggi di programmazione "F#, e "Scala.
Progettazione dettagliata
Espressione is
L'operatore is
viene esteso per testare un'espressione rispetto a un modello .
relational_expression
: is_pattern_expression
;
is_pattern_expression
: relational_expression 'is' pattern
;
Questa forma di relational_expression si aggiunge ai moduli esistenti nella specifica C#. Si tratta di un errore in fase di compilazione se il relational_expression a sinistra del token is
non designa un valore o non ha un tipo.
Ogni identificatore del modello introduce una nuova variabile locale assegnata con certezza dopo che l'operatore is
viene true
(ad esempio, assegnata con certezza quando è vero).
Nota: Tecnicamente esiste un'ambiguità tra il tipo in un
is-expression
e il costante pattern, entrambi i quali potrebbero rappresentare una lettura valida di un identificatore qualificato. Si cerca di associarlo come tipo per la compatibilità con le versioni precedenti della lingua; solo se questo tentaivo fallisce, lo risolviamo come facciamo con un'espressione in altri contesti, cioè con la prima cosa trovata (che deve essere una costante o un tipo). Questa ambiguità è presente solo sul lato destro di un'espressioneis
.
Modelli
I modelli vengono usati nell'operatore is_pattern, in un switch_statemente in un switch_expression per esprimere la forma dei dati in base ai quali devono essere confrontati i dati in ingresso (che chiamiamo valore di input). I modelli possono essere ricorsivi in modo che le parti dei dati possano essere confrontate con i modelli secondari.
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
: '_'
;
Modello di dichiarazione
declaration_pattern
: type simple_designation
;
Il pattern di dichiarazione verifica sia che un'espressione sia di un determinato tipo sia la converte in quel tipo se il test ha esito positivo. Questo può introdurre una variabile locale del tipo indicato con l'identificatore dato, se la designazione è single_variable_designation. Tale variabile locale viene assegnata definitivamente quando il risultato dell'operazione di confronto di schemi è true
.
La semantica di runtime di questa espressione è che testa il tipo di runtime dell'operando sulla sinistra relational_expression rispetto al tipo di nel criterio. Se appartiene a quel tipo di runtime (o un sottotipo) e non null
, il risultato del is operator
è true
.
Alcune combinazioni di tipo statico del lato sinistro e del tipo specificato sono considerate incompatibili e generano un errore in fase di compilazione. Un valore di tipo statico E
viene detto compatibile con criteri con un tipo T
se esiste una conversione di identità, una conversione di riferimento implicita, una conversione boxing, una conversione di riferimento esplicito o una conversione unboxing da E
a T
o se uno di questi tipi è un tipo aperto. Si tratta di un errore in fase di compilazione se un input di tipo E
non è compatibile con il modello con il tipo di un criterio di tipo corrispondente.
Il modello di tipo è utile per l'esecuzione di test di tipi di riferimento in fase di esecuzione e sostituisce il linguaggio
var v = expr as Type;
if (v != null) { // code using v
Con il leggermente più conciso
if (expr is Type v) { // code using v
Si tratta di un errore se il tipo di
Il criterio di tipo può essere usato per testare i valori dei tipi nullable: un valore di tipo Nullable<T>
(o un T
boxed) corrisponde a un criterio di tipo T2 id
se il valore è non-null e il tipo di T2
è T
, o un tipo di base o un'interfaccia di T
. Ad esempio, nel frammento di codice
int? x = 3;
if (x is int v) { // code using v
La condizione dell'istruzione if
è true
in fase di esecuzione e la variabile v
contiene il valore 3
di tipo int
all'interno del blocco. Dopo il blocco la variabile v
è nell'ambito ma non è sicuramente assegnata.
Modello costante
constant_pattern
: constant_expression
;
Un modello costante confronta il valore di un'espressione con un valore costante. La costante può essere qualsiasi espressione costante, ad esempio un valore letterale, il nome di una variabile const
dichiarata o una costante di enumerazione. Quando il valore di input non è un tipo aperto, l'espressione costante viene convertita in modo implicito nel tipo dell'espressione corrispondente; se il tipo del valore di input non è compatibile con i criteri con il tipo dell'espressione costante, l'operazione di corrispondenza dei criteri è un errore.
Il modello c è ritenuto corrispondente al valore di input convertito e se object.Equals(c, e)
restituisce true
.
Ci aspettiamo di vedere e is null
come il modo più comune per testare null
nel codice appena scritto, perché non può richiamare un operator==
definito dall'utente .
"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
;
Se la designazione è una simple_designation, un'espressione e corrisponde al modello. In altre parole, una corrispondenza a un criterio var ha sempre esito positivo con un simple_designation. Se il simple_designation è un single_variable_designation, il valore di e è vincolato a una variabile locale recentemente introdotta. Il tipo della variabile locale è il tipo statico di e.
Se la designazione è un tuple_designation, allora il modello equivale a un positional_pattern della forma (var
designazione, ... )
dove le designazioni sono quelle presenti all'interno delle tuple_designation. Ad esempio, il modello var (x, (y, z))
equivale a (var x, (var y, var z))
.
Si tratta di un errore se il nome var
viene associato a un tipo.
Modello di eliminazione
discard_pattern
: '_'
;
Un'espressione e corrisponde sempre al criterio _
. In altre parole, ogni espressione corrisponde al modello di scarto.
Un modello di scarto non può essere usato come modello di un is_pattern_expression.
Modello posizionale
Un schema posizionale verifica che il valore di input non sia null
, invoca un metodo Deconstruct
appropriato ed esegue ulteriori operazioni di pattern matching sui valori risultanti. Supporta anche una sintassi del modello simile a una tupla (senza il tipo specificato) quando il tipo del valore di input è uguale al tipo contenente Deconstruct
oppure se il tipo del valore di input è un tipo di tupla oppure se il tipo del valore di input è object
o ITuple
e il tipo di runtime dell'espressione implementa ITuple
.
positional_pattern
: type? '(' subpatterns? ')' property_subpattern? simple_designation?
;
subpatterns
: subpattern
| subpattern ',' subpatterns
;
subpattern
: pattern
| identifier ':' pattern
;
Se il tipo viene omesso, lo si accetta come tipo statico del valore di input.
Data la corrispondenza di un valore di input al modello tipo(
subpattern_list)
, un metodo viene selezionato eseguendo una ricerca in tipo per le dichiarazioni accessibili di Deconstruct
e selezionando una tra di esse usando le stesse regole applicate alla dichiarazione di decostruzione.
Si tratta di un errore se un positional_pattern omette il tipo, ha un singolo subpattern secondario senza un identificatore , non ha un property_subpattern e non ha una semplice_designazione. Questo distingue tra un constant_pattern tra parentesi e un positional_pattern.
Per estrarre i valori da confrontare con i modelli nell'elenco,
- Se tipo è stato omesso e il tipo del valore di input è un tipo di tupla, è necessario che il numero di sottopattern sia uguale alla cardinalità della tupla. Ogni elemento della tupla viene confrontato con il sottopattern corrispondentee la corrispondenza ha esito positivo se tutte queste operazioni hanno esito positivo. Se un ha un identificatore , questo deve denominare un elemento della tupla nella posizione corrispondente all'interno del tipo tupla.
- In caso contrario, se esiste un
Deconstruct
appropriato come membro del tipo , si verifica un errore in fase di compilazione se il tipo del valore di input non è compatibile in base al modello con il tipo . In fase di esecuzione, il valore di input viene testato rispetto al tipo . Se questo fallisce, la corrispondenza posizionale fallisce. Se ha successo, il valore di input viene convertito in questo tipo eDeconstruct
viene invocato con nuove variabili generate dal compilatore per ricevere i parametriout
. Ogni valore ricevuto viene confrontato con il corrispondente sottopatterne la corrispondenza ha esito positivo se tutte queste operazioni hanno esito positivo. Se un sottopattern ha un identificatore , allora deve nominare un parametro nella posizione corrispondente diDeconstruct
. - Se invece il tipo è stato omesso e il valore di input è di tipo
object
oITuple
o di un tipo che può essere convertito inITuple
da una conversione di riferimento implicita e non appare alcun identificatore tra i sottopattern, la corrispondenza si effettua usandoITuple
. - In caso contrario, il modello è un errore in fase di compilazione.
L'ordine in cui i sottopattern vengono confrontati in fase di esecuzione non è specificato e un mancato abbinamento potrebbe non tentare di abbinare tutti i sottopattern.
Esempio
Questo esempio usa molte delle funzionalità descritte in questa specifica
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 };
Modello di proprietà
Un criterio di proprietà verifica che il valore di input non sia null
e corrisponde in modo ricorsivo ai valori estratti tramite l'uso di proprietà o campi accessibili.
property_pattern
: type? property_subpattern simple_designation?
;
property_subpattern
: '{' '}'
| '{' subpatterns ','? '}'
;
Si tratta di un errore se un sottoschema di un property_pattern non contiene un identificatore (deve essere nella seconda forma, che include un identificatore ). Una virgola finale dopo l'ultimo sottopattern è facoltativa.
Si noti che un modello di controllo null non rientra in un modello di proprietà semplice. Per verificare se la stringa s
non è null, è possibile scrivere uno dei formati seguenti
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 {}) ...
Data una corrispondenza di un'espressione e al modello di tipo{
property_pattern_list}
, avviene un errore di compilazione se l'espressione e non è compatibile con il modello del tipo T designato dal tipo . Se il tipo è assente, viene preso come tipo statico di e. Se l'identificatore è presente, dichiara una variabile di modello del tipo . Ognuno degli identificatori visualizzati sul lato sinistro della property_pattern_list deve designare una proprietà leggibile accessibile o un campo di T. Se la simple_designation del property_pattern è presente, definisce una variabile di modello di tipo T.
In fase di esecuzione, l'espressione viene testata su T. Se questo fallisce, la corrispondenza del criterio di proprietà non riesce e il risultato è false
. Se ha successo, ogni campo o proprietà property_subpattern viene letto e il suo valore viene confrontato con il modello corrispondente. Il risultato dell'intera partita è false
solo se il risultato di una di queste è false
. L'ordine in cui i sottopattern vengono confrontati non è specificato e una corrispondenza non riuscita potrebbe non corrispondere a tutti i sottopattern in fase di esecuzione. Se la corrispondenza ha esito positivo e il simple_designation del property_pattern è un single_variable_designation, definisce una variabile di tipo T a cui viene assegnato il valore corrispondente.
Nota: il modello di proprietà può essere utilizzato per la corrispondenza di modelli con tipi anonimi.
Esempio
if (o is string { Length: 5 } s)
Espressione switch
Viene aggiunta una switch_expression per supportare una semantica simile a switch
per un contesto di espressioni.
La sintassi del linguaggio C# è aumentata con le produzioni sintattiche seguenti:
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
;
Il switch_expression non è consentito come expression_statement.
Stiamo considerando di allentarlo in una revisione futura.
Il tipo di switch_expression è il tipo comune migliore (§12.6.3.15) delle espressioni che appaiono a destra dei token =>
del switch_expression_armse tale tipo esiste e l'espressione in ogni arm dell'espressione switch può essere convertita in modo implicito in tale tipo. Si aggiunge inoltre una nuova conversione dell'espressione switch , che è una conversione implicita predefinita da un'espressione switch a ogni tipo T
per cui esiste una conversione implicita dall'espressione di ogni ramo a T
.
Si tratta di un errore se il modello di switch_expression_armnon può influire sul risultato perché alcuni modelli e guard precedenti corrisponderanno sempre.
Un'espressione switch viene detta esaustiva se un braccio dell'espressione switch gestisce ogni valore del relativo input. Il compilatore genera un avviso se un'espressione switch non è esaustiva.
Durante il runtime, il risultato dell'`switch_expression` è il valore dell'espressione `` `` del primo `switch_expression_arm` in cui l'espressione sul lato sinistro dell'`switch_expression` corrisponde al modello del `switch_expression_arm` e in cui il `case_guard` del `switch_expression_arm`, se presente, restituisce `true
`. Se tale switch_expression_armnon esiste , il switch_expression genera un'istanza dell'eccezione System.Runtime.CompilerServices.SwitchExpressionException
.
Parens facoltativo quando si attiva un valore letterale di tupla
Per attivare un valore letterale di tupla usando l'switch_statement, è necessario scrivere quelle che apparentemente sono parentesi ridondanti.
switch ((a, b))
{
Consentire
switch (a, b)
{
Le parentesi dell'istruzione switch sono facoltative quando l'espressione attivata è un valore letterale di tupla.
Ordine di valutazione nella corrispondenza di modelli
Concedere al compilatore flessibilità nel riordinare le operazioni eseguite durante il pattern-matching può consentire una flessibilità che può essere usata per migliorare l'efficienza del pattern-matching. Il requisito (non applicato) sarebbe che le proprietà a cui si accede in un modello e i metodi Deconstruct devono essere privi di effetti collaterali, idempotenti, ecc., cioè "puri". Ciò non significa che aggiungeremo purezza come concetto di linguaggio, ma solo che consentiremmo la flessibilità del compilatore nelle operazioni di riordinamento.
Risoluzione 2018-04-04 LDM: confermato: il compilatore è autorizzato a modificare l'ordine delle chiamate a Deconstruct
, degli accessi alle proprietà e delle invocazioni dei metodi in ITuple
e può presumere che i valori restituiti siano identici in più chiamate. Il compilatore non dovrebbe richiamare funzioni che non possono influire sul risultato, e saremo molto attenti prima di apportare qualsiasi modifica all'ordine di valutazione generato dal compilatore in futuro.
Alcune possibili ottimizzazioni
La compilazione del pattern matching può sfruttare le parti comuni dei modelli. Ad esempio, se il test del tipo di livello superiore di due pattern successivi in un switch_statement è dello stesso tipo, il codice generato può saltare il test del tipo per il secondo pattern.
Quando alcuni dei modelli sono numeri interi o stringhe, il compilatore può generare lo stesso tipo di codice generato per un'istruzione switch nelle versioni precedenti del linguaggio.
Per altre informazioni su questi tipi di ottimizzazioni, vedere [Scott e Ramsey (2000)].
C# feature specifications