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.
13.1 Generale
C# offre un'ampia gamma di istruzioni.
Nota: la maggior parte di queste istruzioni sarà familiare agli sviluppatori che hanno programmato in C e C++. nota finale
statement
: labeled_statement
| declaration_statement
| embedded_statement
;
embedded_statement
: block
| empty_statement
| expression_statement
| selection_statement
| iteration_statement
| jump_statement
| try_statement
| checked_statement
| unchecked_statement
| lock_statement
| using_statement
| yield_statement
| unsafe_statement // unsafe code support
| fixed_statement // unsafe code support
;
unsafe_statement (§24.2) e fixed_statement (§24.7) sono disponibili solo nel codice non sicuro (§24).
Il embedded_statement non terminale viene usato per le istruzioni che compaiono all'interno di altre istruzioni. L'uso di embedded_statement anziché statement esclude l'impiego di istruzioni di dichiarazione e istruzioni etichettate in questi contesti.
Esempio: codice
void F(bool b) { if (b) int i = 44; }genera un errore in fase di compilazione perché un'istruzione
ifrichiede un embedded_statement anziché un'istruzione per il relativoiframo. Se questo codice fosse consentito, la variabileiverrebbe dichiarata, ma non potrà mai essere usata. Si noti, tuttavia, che inserendoila dichiarazione in un blocco, l'esempio è valido.esempio finale
13.2 Punti finali e raggiungibilità
Ogni istruzione ha un punto finale. In termini intuitivi, il punto finale di un'istruzione è la posizione che segue immediatamente l'istruzione . Le regole di esecuzione per istruzioni composite (istruzioni che contengono istruzioni incorporate) specificano l'azione eseguita quando il controllo raggiunge il punto finale di un'istruzione incorporata.
Esempio: quando il controllo raggiunge il punto finale di un'istruzione in un blocco, il controllo viene trasferito all'istruzione successiva nel blocco. esempio finale
Se un'istruzione può essere raggiunta dall'esecuzione, l'istruzione viene considerata raggiungibile. Viceversa, se non è possibile che venga eseguita un'istruzione, l'istruzione viene considerata non raggiungibile.
Esempio: nel codice seguente
void F() { Console.WriteLine("reachable"); goto Label; Console.WriteLine("unreachable"); Label: Console.WriteLine("reachable"); }La seconda chiamata di Console.WriteLine non è raggiungibile perché non è possibile che l'istruzione venga eseguita.
esempio finale
Viene segnalato un avviso se un'istruzione diversa da throw_statement, blocco o empty_statement non è raggiungibile. Non è in particolare un errore che un'istruzione non sia raggiungibile.
Nota: per determinare se una determinata istruzione o un punto finale è raggiungibile, un compilatore esegue l'analisi del flusso in base alle regole di raggiungibilità definite per ogni istruzione. L'analisi del flusso tiene conto dei valori delle espressioni costanti (§12.25) che controllano il comportamento delle istruzioni, ma i possibili valori delle espressioni non costanti non vengono considerati. In altre parole, ai fini dell'analisi del flusso di controllo, un'espressione non costante di un determinato tipo viene considerata come qualsiasi valore possibile di tale tipo.
Nell'esempio
void F() { const int i = 1; if (i == 2) Console.WriteLine("unreachable"); }L'espressione booleana dell'istruzione è un'espressione
ifcostante perché entrambi gli operandi dell'operatore==sono costanti. Poiché l'espressione costante viene valutata in fase di compilazione, producendo il valorefalse, laConsole.WriteLinechiamata viene considerata non raggiungibile. Tuttavia, seiviene modificato come variabile localevoid F() { int i = 1; if (i == 2) Console.WriteLine("reachable"); }l'invocazione
Console.WriteLineè ritenuta raggiungibile, sebbene in realtà non verrà mai eseguita.nota finale
Il blocco di un membro di funzione o di una funzione anonima è sempre considerato raggiungibile. Valutando successivamente le regole di raggiungibilità di ogni istruzione in un blocco, è possibile determinare la raggiungibilità di qualsiasi istruzione specificata.
Esempio: nel codice seguente
void F(int x) { Console.WriteLine("start"); if (x < 0) Console.WriteLine("negative"); }la raggiungibilità del secondo
Console.WriteLineviene determinata nel modo seguente:
- La prima
Console.WriteLineistruzione di espressione è raggiungibile perché il blocco delFmetodo è raggiungibile (§13.3).- Il punto finale della prima
Console.WriteLineistruzione dell'espressione è raggiungibile perché tale istruzione è raggiungibile (§13.7 e §13.3).- L'istruzione
ifè raggiungibile perché il punto finale della primaConsole.WriteLineistruzione dell'espressione è raggiungibile (§13.7 e §13.3).- La seconda
Console.WriteLineistruzione di espressione è raggiungibile perché l'espressione booleana dell'istruzioneifnon ha il valore costantefalse.esempio finale
Esistono due situazioni in cui si tratta di un errore in fase di compilazione per il punto finale di un'istruzione che può essere raggiungibile:
Poiché l'istruzione
switchnon consente a una sezione switch di passare alla sezione switch successiva, è un errore di compilazione se il punto finale dell'elenco delle istruzioni di una sezione switch è raggiungibile. Se si verifica questo errore, in genere è un'indicazione che manca un'istruzionebreak.Si tratta di un errore in fase di compilazione per il punto finale del blocco di un membro della funzione o di una funzione anonima che calcola un valore che può essere raggiungibile. Se si verifica questo errore, in genere è un'indicazione che manca un'istruzione
return(§13.10.5).
13.3 Blocchi
13.3.1 Generale
Un blocco consente di scrivere più istruzioni in contesti che normalmente ne permetterebbero una sola.
block
: '{' statement_list? '}'
;
Un blocco è costituito da un statement_list facoltativo (§13.3.2), racchiuso tra parentesi graffe. Se l'elenco di istruzioni viene omesso, il blocco è considerato vuoto.
Un blocco può contenere istruzioni di dichiarazione (§13.6). L'ambito di una variabile locale o di una costante dichiarata in un blocco è il blocco .
Un blocco viene eseguito come segue:
- Se il blocco è vuoto, il controllo viene trasferito al punto finale del blocco.
- Se il blocco non è vuoto, il controllo viene trasferito all'elenco di istruzioni. Quando e se il controllo raggiunge l'estremità finale dell'elenco di istruzioni, il controllo viene trasferito al punto finale del blocco.
L'elenco di istruzioni di un blocco è raggiungibile se il blocco stesso è raggiungibile.
Il punto finale di un blocco è raggiungibile se il blocco è vuoto o se il punto finale dell'elenco di istruzioni è raggiungibile.
Un blocco che contiene una o più yield istruzioni (§13.15) viene chiamato blocco iteratore. I blocchi iteratori vengono usati per implementare i membri della funzione come iteratori (§15.15). Alcune restrizioni aggiuntive si applicano ai blocchi iteratori:
- Si tratta di un errore in fase di compilazione la presenza di un'istruzione
returnin un blocco iteratore (ma le istruzioniyield returnsono consentite). - Si tratta di un errore in fase di compilazione per un blocco iteratore che contiene un contesto non sicuro (§24.2). Un blocco iteratore definisce sempre un contesto sicuro, anche quando la relativa dichiarazione è annidata in un contesto non sicuro.
13.3.2 Elenchi di istruzioni
Un elenco di istruzioni è costituito da una o più istruzioni scritte in sequenza. Gli elenchi di dichiarazioni sono presenti nei blocchi (§13.3) e nei switch_block (§13.8.3).
statement_list
: statement+
;
Un elenco di istruzioni viene eseguito trasferendo il controllo alla prima istruzione. Quando e se il controllo raggiunge il punto finale di un'istruzione, il controllo viene trasferito all'istruzione successiva. Quando e se il controllo raggiunge il punto finale dell'ultima istruzione, il controllo viene trasferito al punto finale dell'elenco di istruzioni.
Un comando in una lista di istruzioni è accessibile se almeno una delle seguenti condizioni è vera.
- La dichiarazione è la prima dichiarazione ed è raggiungibile l'elenco delle dichiarazioni stesso.
- Il punto finale dell'istruzione precedente è raggiungibile.
- L'istruzione è un'istruzione etichettata e l'etichetta viene fatto riferimento da un'istruzione raggiungibile
goto.
Il punto finale di un elenco di istruzioni è raggiungibile se il punto finale dell'ultima istruzione nell'elenco è raggiungibile.
13.4 Istruzione vuota
Un empty_statement non esegue alcuna operazione.
empty_statement
: ';'
;
Un'istruzione vuota viene usata quando non sono presenti operazioni da eseguire in un contesto in cui è necessaria un'istruzione .
L'esecuzione di un'istruzione vuota trasferisce semplicemente il controllo al punto finale dell'istruzione. Pertanto, il punto finale di un'istruzione vuota è raggiungibile se l'istruzione vuota è raggiungibile.
Esempio: un'istruzione vuota può essere usata durante la scrittura di un'istruzione
whilecon un corpo Null:bool ProcessMessage() {...} void ProcessMessages() { while (ProcessMessage()) ; }È anche possibile usare un'istruzione vuota per dichiarare un'etichetta subito prima della chiusura "
}" di un blocco:void F(bool done) { ... if (done) { goto exit; } ... exit: ; }esempio finale
13.5 Istruzioni etichettate
Un labeled_statement consente a un'istruzione di essere preceduta da un'etichetta. Le istruzioni etichettate sono consentite in blocchi, ma non sono consentite come istruzioni incorporate.
labeled_statement
: identifier ':' statement
;
Un'istruzione etichettata dichiara un'etichetta con il nome specificato dall'identificatore. L'ambito di un'etichetta è l'intero blocco in cui viene dichiarata l'etichetta, inclusi i blocchi annidati. È un errore in fase di compilazione se due etichette con lo stesso nome hanno ambiti sovrapposti.
Le istruzioni goto possono fare riferimento a un'etichetta (§13.10.4) nel contesto dell'etichetta.
Nota: ciò significa che
gotole istruzioni possono trasferire il controllo all'interno e all'esterno dei blocchi, ma mai all'interno dei blocchi. nota finale
Le etichette hanno uno spazio di dichiarazione personalizzato e non interferiscono con altri identificatori.
Esempio: esempio
int F(int x) { if (x >= 0) { goto x; } x = -x; x: return x; }è valido e usa il nome x sia come parametro che come etichetta.
esempio finale
L'esecuzione di un'istruzione etichettata corrisponde esattamente all'esecuzione dell'istruzione che segue l'etichetta.
Oltre alla raggiungibilità fornita dal normale flusso di controllo, un'istruzione etichettata è raggiungibile se l'etichetta viene fatto riferimento da un'istruzione raggiungibile goto , a meno che l'istruzione goto non si trova all'interno del try blocco o di un catch blocco di un try_statement che includa un finally blocco il cui punto finale non è raggiungibile e l'istruzione etichettata non si trova all'esterno del try_statement.
13.6 Dichiarazioni
13.6.1 Generale
Un declaration_statement dichiara una o più variabili locali, una o più costanti locali o una funzione locale. Le istruzioni di dichiarazione sono consentite nei blocchi e nei blocchi switch, ma non sono consentite come istruzioni annidate.
declaration_statement
: local_variable_declaration ';'
| local_constant_declaration ';'
| local_function_declaration
;
Una variabile locale viene dichiarata utilizzando un local_variable_declaration (§13.6.2). Una costante locale viene dichiarata utilizzando un local_constant_declaration (§13.6.3). Una funzione locale viene dichiarata utilizzando un local_function_declaration (§13.6.4).
I nomi dichiarati vengono introdotti nello spazio di dichiarazione di inclusione più vicino (§7.3).
13.6.2 Dichiarazioni di variabili locali
13.6.2.1 Generale
Un local_variable_declaration dichiara una o più variabili locali.
local_variable_declaration
: implicitly_typed_local_variable_declaration
| explicitly_typed_local_variable_declaration
| explicitly_typed_ref_local_variable_declaration
;
Le dichiarazioni tipizzate in modo implicito contengono la parola chiave contestuale (§6.4.4) var con conseguente ambiguità sintattica tra le tre categorie risolte nel modo seguente:
- Se nell'ambito non è presente alcun tipo denominato
vare l'input corrisponde a implicitly_typed_local_variable_declaration, allora questo viene scelto. - In caso contrario, se un tipo denominato
varè nell'ambito, implicitly_typed_local_variable_declaration non viene considerato come una corrispondenza possibile.
All'interno di un local_variable_declaration ogni variabile viene introdotta da un dichiaratore, che è uno dei implicitly_typed_local_variable_declarator, explicitly_typed_local_variable_declarator o ref_local_variable_declarator per variabili locali tipizzate in modo implicito, tipizzate in modo esplicito o variabili locali con riferimento, rispettivamente. Il dichiaratore definisce il nome (identificatore) e il valore iniziale, se presente, della variabile introdotta.
Se sono presenti più elementi dichiaranti in una dichiarazione, vengono elaborati, incluse le espressioni di inizializzazione, da sinistra a destra (§9.4.4.5).
Nota: per un local_variable_declaration che non si verifica come for_initializer (§13.9.4) o resource_acquisition (§13.14) questo ordine da sinistra a destra equivale a ogni dichiaratore all'interno di un local_variable_declaration separato. Ad esempio:
void F() { int x = 1, y, z = x * 2; }Equivale a:
void F() { int x = 1; int y; int z = x * 2; }nota finale
Il valore di una variabile locale viene ottenuto in un'espressione utilizzando un simple_name (§12.8.4). Una variabile locale deve essere assegnata in modo definitivo (§9,4) in ogni posizione in cui viene ottenuto il relativo valore. Ogni variabile locale introdotta da un local_variable_declaration viene inizialmente non assegnata (§9.4.3). Se un dichiaratore ha un'espressione di inizializzazione, la variabile locale introdotta viene classificata come assegnata alla fine del dichiaratore (§9.4.4.5).
L'ambito di una variabile locale introdotta da un local_variable_declaration è definito come segue (§7.7):
- Se la dichiarazione si verifica come for_initializer, l'ambito è il for_initializer, for_condition, for_iterator e embedded_statement (§13.9.4);
- Se la dichiarazione si verifica come resource_acquisition , l'ambito è il blocco più esterno dell'espansione semanticamente equivalente del using_statement (§13.14);
- In caso contrario, l'ambito è il blocco in cui si verifica la dichiarazione.
Si tratta di un errore per fare riferimento a una variabile locale in base al nome in una posizione testuale che precede il dichiaratore o all'interno di qualsiasi espressione di inizializzazione all'interno del relativo dichiaratore. Nell'ambito di una variabile locale, si tratta di un errore in fase di compilazione per dichiarare un'altra variabile locale, una funzione locale o una costante con lo stesso nome.
Il contesto ref-safe (§9.7.2) di una variabile locale ref è il contesto ref-safe della sua inizializzazione variable_reference. Il contesto ref-safe delle variabili locali non ref è declaration-block.
13.6.2.2 Dichiarazioni di variabili locali tipizzate in modo implicito
implicitly_typed_local_variable_declaration
: 'var' implicitly_typed_local_variable_declarator
| ref_kind 'var' ref_local_variable_declarator
;
implicitly_typed_local_variable_declarator
: identifier '=' expression
;
Un implicitly_typed_local_variable_declaration introduce una singola variabile locale, identificatore. L'espressione o variable_reference deve avere un tipo in fase di compilazione, T. La prima alternativa dichiara una variabile con un valore iniziale di expression; il relativo tipo è T? quando T è un tipo riferimento non nullable; in caso contrario, il tipo è T. La seconda alternativa dichiara una variabile ref con un valore iniziale di refvariable_reference; il suo tipo è ref T? quando T è un tipo riferimento non annullabile; in caso contrario, il suo tipo è ref T. (ref_kind è descritto in §15.6.1.)
Esempio:
var i = 5; var s = "Hello"; var d = 1.0; var numbers = new int[] {1, 2, 3}; var orders = new Dictionary<int,Order>(); ref var j = ref i; ref readonly var k = ref i;Le dichiarazioni di variabili locali tipizzate in modo implicito sopra sono esattamente equivalenti alle dichiarazioni tipizzate in modo esplicito seguenti:
int i = 5; string s = "Hello"; double d = 1.0; int[] numbers = new int[] {1, 2, 3}; Dictionary<int,Order> orders = new Dictionary<int,Order>(); ref int j = ref i; ref readonly int k = ref i;Di seguito sono riportate dichiarazioni errate di variabili locali tipizzate in modo implicito.
var x; // Error, no initializer to infer type from var y = {1, 2, 3}; // Error, array initializer not permitted var z = null; // Error, null does not have a type var u = x => x + 1; // Error, anonymous functions do not have a type var v = v++; // Error, initializer cannot refer to v itselfesempio finale
13.6.2.3 Dichiarazioni di variabili locali tipate in modo esplicito
explicitly_typed_local_variable_declaration
: type explicitly_typed_local_variable_declarators
;
explicitly_typed_local_variable_declarators
: explicitly_typed_local_variable_declarator
(',' explicitly_typed_local_variable_declarator)*
;
explicitly_typed_local_variable_declarator
: identifier ('=' local_variable_initializer)?
;
local_variable_initializer
: expression
| array_initializer
;
Un explicitly_typed_local_variable_declaration introduce una o più variabili locali con il tipo specificato.
Se è presente un local_variable_initializer , il tipo deve essere appropriato in base alle regole di assegnazione semplice (§12.23.2) o all'inizializzazione della matrice (§17.7) e il relativo valore viene assegnato come valore iniziale della variabile.
13.6.2.4 Dichiarazioni di variabili locali di riferimento tipizzata in modo esplicito
explicitly_typed_ref_local_variable_declaration
: ref_kind type ref_local_variable_declarators
;
ref_local_variable_declarators
: ref_local_variable_declarator (',' ref_local_variable_declarator)*
;
ref_local_variable_declarator
: identifier '=' 'ref' variable_reference
;
L'inizializzazione variable_reference deve avere tipo e soddisfare gli stessi requisiti di un'assegnazione di riferimento (§12.23.3).
Se ref_kind è ref readonly, gli identificatori dichiarati sono riferimenti a variabili trattate come di sola lettura. In caso contrario, se ref_kind è ref, gli identificatoridichiarati sono riferimenti alle variabili che devono essere scrivibili.
È un errore in fase di compilazione dichiarare una variabile ref locale o una variabile di tipo ref struct, all'interno di un metodo dichiarato con il modificatore method_modifierasync, o all'interno di un iteratore (§15.15).
13.6.3 Dichiarazioni costanti locali
Un local_constant_declaration dichiara una o più costanti locali.
local_constant_declaration
: 'const' type constant_declarators
;
constant_declarators
: constant_declarator (',' constant_declarator)*
;
constant_declarator
: identifier '=' constant_expression
;
Il tipo di un local_constant_declaration specifica il tipo delle costanti introdotte dalla dichiarazione. Il tipo è seguito da un elenco di constant_declarators, ognuno dei quali introduce una nuova costante. Un constant_declarator è costituito da un identificatore che assegna un nome alla costante, seguito da un token "=", seguito da un constant_expression (§12.25) che assegna il valore della costante.
Il tipo e constant_expression di una dichiarazione costante locale seguono le stesse regole di quelle di una dichiarazione di membro costante (§15.4).
Il valore di una costante locale viene ottenuto in un'espressione utilizzando un simple_name (§12.8.4).
L'ambito di una costante locale è il blocco in cui si verifica la dichiarazione. Si tratta di un errore per fare riferimento a una costante locale in una posizione testuale che precede la fine del relativo constant_declarator.
Una dichiarazione costante locale che dichiara più costanti equivale a più dichiarazioni di singole costanti con lo stesso tipo.
13.6.4 Dichiarazioni di funzioni locali
Un local_function_declaration dichiara una funzione locale.
local_function_declaration
: local_function_modifier* return_type local_function_header
local_function_body
| ref_local_function_modifier* ref_kind ref_return_type
local_function_header ref_local_function_body
;
local_function_header
: identifier '(' parameter_list? ')'
| identifier type_parameter_list '(' parameter_list? ')'
type_parameter_constraints_clause*
;
local_function_modifier
: ref_local_function_modifier
| 'async'
;
ref_local_function_modifier
: 'static'
| unsafe_modifier // unsafe code support
;
local_function_body
: block
| '=>' null_conditional_invocation_expression ';'
| '=>' expression ';'
;
ref_local_function_body
: block
| '=>' 'ref' variable_reference ';'
;
Nota grammaticale: quando si riconosce un local_function_body se entrambe le alternative di null_conditional_invocation_expression ed espressione sono applicabili, il primo deve essere scelto. (§15.6.1)
Esempio: esistono due casi d'uso comuni per le funzioni locali: metodi iteratore e metodi asincroni. Per i metodi iterator le eccezioni vengono riscontrate solo quando si chiama il codice che enumera la sequenza restituita. Nei metodi asincroni, qualsiasi eccezione viene osservata solo quando si attende l'attività restituita. L'esempio seguente illustra la separazione tra convalida dei parametri e implementazione dell'iteratore usando una funzione locale:
public static IEnumerable<char> AlphabetSubset(char start, char end) { if (start < 'a' || start > 'z') { throw new ArgumentOutOfRangeException(paramName: nameof(start), message: "start must be a letter"); } if (end < 'a' || end > 'z') { throw new ArgumentOutOfRangeException(paramName: nameof(end), message: "end must be a letter"); } if (end <= start) { throw new ArgumentException( $"{nameof(end)} must be greater than {nameof(start)}"); } return AlphabetSubsetImplementation(); IEnumerable<char> AlphabetSubsetImplementation() { for (var c = start; c < end; c++) { yield return c; } } }esempio finale
Se non diversamente specificato di seguito, la semantica di tutti gli elementi grammaticali è uguale a quella di method_declaration (§15.6.1), letta nel contesto di una funzione locale anziché di un metodo.
L'identificatore di un local_function_declaration deve essere univoco nell'ambito del blocco dichiarato, inclusi gli spazi di dichiarazione delle variabili locali che racchiudono. Una conseguenza di questo è che le dichiarazioni_funzioni_locali sovraccaricate non sono consentite.
Un local_function_declaration può includere un async modificatore (§15.14) e un unsafe modificatore (§24.1). Se la dichiarazione include il async modificatore, il tipo restituito sarà void o un «TaskType» tipo (§15.14.1). Se la dichiarazione include il static modificatore, la funzione è una funzione locale statica; in caso contrario, è una funzione locale non statica. Si tratta di un errore in fase di compilazione che type_parameter_list o parameter_list contenga attributi. Se la funzione locale viene dichiarata in un contesto non sicuro (§24.2), la funzione locale può includere codice unsafe, anche se la dichiarazione della funzione locale non include il unsafe modificatore.
La funzione locale è dichiarata all'interno dell'ambito di un blocco. Una funzione locale non statica può acquisire variabili dall'ambito di inclusione mentre una funzione locale statica non deve (pertanto non ha accesso alla inclusione di variabili locali, parametri, funzioni locali non statiche o this). Si tratta di un errore in fase di compilazione se una variabile acquisita viene letta dal corpo di una funzione locale non statica, ma non viene assegnata sicuramente prima di ogni chiamata alla funzione. Un compilatore determina quali variabili sono sicuramente assegnate in caso di restituzione (§9.4.4.33).
Quando il tipo di this è un tipo struct, è un errore in fase di compilazione nel corpo di una funzione locale accedere a this. Ciò vale se l'accesso è esplicito (come in this.x) o implicito (come in x dove x è un membro dell'istanza dello struct). Questa regola impedisce solo tale accesso e non influisce sul fatto che la ricerca dei membri restituisca un membro dello struct.
Si verifica un errore in fase di compilazione se il corpo della funzione locale contiene un'istruzione goto, un'istruzione break o un'istruzione continue la cui destinazione è esterna al corpo della funzione locale.
Nota: le regole precedenti per
thisegotorispecchiano le regole per le funzioni anonime in §12.21.3. nota finale
Una funzione locale può essere chiamata da un punto lessicale prima della relativa dichiarazione. Tuttavia, si tratta di un errore in fase di compilazione per la funzione da dichiarare lessicalmente prima della dichiarazione di una variabile usata nella funzione locale (§7.7).
Si tratta di un errore in fase di compilazione per una funzione locale per dichiarare un parametro, un parametro di tipo o una variabile locale con lo stesso nome di uno dichiarato in qualsiasi spazio di dichiarazione di variabile locale contenitore.
I corpi delle funzioni locali sono sempre raggiungibili. L'endpoint di una dichiarazione di funzione locale è raggiungibile se il punto iniziale della dichiarazione di funzione locale è raggiungibile.
Esempio: nell'esempio seguente il corpo di
Lè raggiungibile anche se il punto iniziale diLnon è raggiungibile. Poiché il punto iniziale diLnon è raggiungibile, l'istruzione che segue l'endpoint diLnon è raggiungibile:class C { int M() { L(); return 1; // Beginning of L is not reachable int L() { // The body of L is reachable return 2; } // Not reachable, because beginning point of L is not reachable return 3; } }In altre parole, la posizione di una dichiarazione di funzione locale non influisce sulla raggiungibilità di alcuna istruzione nella funzione contenitore. esempio finale
Se il tipo dell'argomento di una funzione locale è dynamic, la funzione da chiamare deve essere risolta in fase di compilazione, non in fase di esecuzione.
Una funzione locale non deve essere utilizzata in un albero delle espressioni.
Una funzione locale statica
- Può fare riferimento a membri statici, parametri di tipo, definizioni costanti e funzioni locali statiche dall'ambito di inclusione.
- Non fare riferimento a
thisné ai membri d'istanza da un riferimento implicitobase, né alle variabili locali, ai parametri o alle funzioni locali non statiche dall'ambito circostante. Tuttavia, tutte queste sono consentite in un'espressionenameof().
13.7 Istruzioni di espressione
Un expression_statement valuta un'espressione specificata. Il valore calcolato dall'espressione, se presente, viene rimosso.
expression_statement
: statement_expression ';'
;
statement_expression
: null_conditional_invocation_expression
| invocation_expression
| object_creation_expression
| assignment
| post_increment_expression
| post_decrement_expression
| pre_increment_expression
| pre_decrement_expression
| await_expression
;
Non tutte le espressioni sono consentite come dichiarazioni.
Nota: in particolare, le espressioni come
x + yex == 1, che calcolano semplicemente un valore (che verrà rimosso), non sono consentite come istruzioni. nota finale
L'esecuzione di un expression_statement valuta l'espressione contenuta e quindi trasferisce il controllo al punto finale del expression_statement. Il punto finale di un expression_statement è raggiungibile se tale expression_statement è raggiungibile.
13.8 Istruzioni di selezione
13.8.1 Generale
Le istruzioni di selezione selezionano una delle possibili istruzioni per l'esecuzione in base al valore di un'espressione.
selection_statement
: if_statement
| switch_statement
;
13.8.2 Istruzione if
L'istruzione if seleziona un'istruzione per l'esecuzione in base al valore di un'espressione booleana.
if_statement
: 'if' '(' boolean_expression ')' embedded_statement
| 'if' '(' boolean_expression ')' embedded_statement
'else' embedded_statement
;
Una else parte è associata al precedente if lessicalmente più vicino consentito dalla sintassi.
Esempio: di conseguenza, un'istruzione
ifdel formif (x) if (y) F(); else G();equivale a
if (x) { if (y) { F(); } else { G(); } }esempio finale
Un'istruzione if viene eseguita nel modo seguente:
- Il boolean_expression (§12.26) viene valutato.
- Se l'espressione booleana restituisce
true, il controllo viene trasferito alla prima istruzione incorporata. Quando e se il controllo raggiunge il punto finale dell'istruzione, il controllo viene trasferito al punto finale dell'istruzioneif. - Se l'espressione booleana restituisce
falsee se è presente unaelseparte, il controllo viene trasferito alla seconda istruzione incorporata. Quando e se il controllo raggiunge il punto finale dell'istruzione, il controllo viene trasferito al punto finale dell'istruzioneif. - Se l'espressione booleana restituisce
falsee se unaelseparte non è presente, il controllo viene trasferito al punto finale dell'istruzioneif.
La prima istruzione incorporata di un'istruzione if è raggiungibile se l'istruzione if è raggiungibile e l'espressione booleana non ha il valore falsecostante .
La seconda istruzione incorporata di un'istruzione if , se presente, è raggiungibile se l'istruzione if è raggiungibile e l'espressione booleana non ha il valore truecostante .
Il punto finale di un'istruzione if è raggiungibile se il punto finale di almeno una delle relative istruzioni incorporate è raggiungibile. Inoltre, il punto finale di un'istruzione if senza else parti è raggiungibile se l'istruzione if è raggiungibile e l'espressione booleana non ha il valore truecostante .
13.8.3 Istruzione switch
L'istruzione switch seleziona per l'esecuzione di un elenco di istruzioni con un'etichetta switch associata che corrisponde al valore del selector_expression dell'opzione.
switch_statement
: 'switch' selector_expression switch_block
;
selector_expression
: '(' expression ')'
| tuple_expression
;
switch_block
: '{' switch_section* '}'
;
switch_section
: switch_label+ statement_list
;
switch_label
: 'case' pattern case_guard? ':'
| 'default' ':'
;
case_guard
: 'when' null_coalescing_expression
;
Un switch_statement è costituito dalla parola chiave switch, seguita da un'espressione tuple_expression o tra parentesi (ognuna delle quali è denominata selector_expression), seguita da un switch_block. Il switch_block è costituito da zero o più switch_sections, racchiusi tra parentesi graffe. Ogni switch_section è costituito da uno o più switch_labelseguiti da un statement_list (§13.3.2). Ogni switch_label contenente case ha un modello associato (§11) rispetto al quale viene testato il valore del selector_expression del commutatore. Se case_guard è presente, l'espressione deve essere convertibile in modo implicito nel tipo bool e tale espressione viene valutata come condizione aggiuntiva per il caso da considerare soddisfatta.
Nota: per praticità, le parentesi in switch_statement possono essere omesse quando il selector_expression è un tuple_expression. Ad esempio, è possibile scrivere
switch ((a, b)) …nel formatoswitch (a, b) …. nota finale
Il tipo di governance di un'istruzione switch viene stabilito dal selector_expression dell'opzione.
- Se il tipo di selector_expression dell'opzione è
sbyte, ,shortbyte,charulonglongintushortboolstringuinto un enum_type oppure se è il tipo di valore nullable corrispondente a uno di questi tipi, questo è il tipo di controllo dell'istruzione.switch - In caso contrario, se esiste esattamente una conversione implicita definita dall'utente dal tipo di selector_expression dell'opzione a uno dei tipi di controllo seguenti:
sbyte,shortlongstringushortbyteuintulongintcharo un tipo di valore nullable corrispondente a uno di questi tipi, il tipo convertito è il tipo di controllo dell'istruzione.switch - In caso contrario, il tipo di controllo dell'istruzione
switchè il tipo di selector_expression dell'opzione. Si tratta di un errore se non esiste alcun tipo di questo tipo.
Può essere presente al massimo un'etichetta default in un'istruzione switch .
Si tratta di un errore se il modello di un'etichetta switch non è applicabile (§11.2.1) al tipo dell'espressione di input.
Si tratta di un errore se il modello di un'etichetta di switch è sussunto da (§11.3) l'insieme di modelli di etichette di switch precedenti nello stesso switch che non hanno un case guard o il cui case guard è un'espressione costante con valore true.
Esempio:
switch (shape) { case var x: break; case var _: // error: pattern subsumed, as previous case always matches break; default: break; // warning: unreachable, all possible values already handled. }esempio finale
Un'istruzione switch viene eseguita come segue:
- Il selector_expression dell'opzione viene valutato e convertito nel tipo di controllo.
- Il controllo viene trasferito in base al valore del selector_expression dell'opzione convertita:
- Il primo criterio lessicalmente nel set di
caseetichette nella stessaswitchistruzione che corrisponde al valore del selector_expression dell'opzione e per il quale l'espressione di protezione è assente o restituisce true, fa sì che il controllo venga trasferito all'elenco di istruzioni dopo l'etichetta corrispondentecase. - In caso contrario, se è presente un'etichetta
default, il controllo viene trasferito all'elenco di istruzioni che segue l'etichettadefault. - In caso contrario, il controllo viene trasferito al punto finale dell'istruzione
switch.
- Il primo criterio lessicalmente nel set di
Nota: l'ordine in cui i criteri vengono confrontati in fase di esecuzione non è definito. Un compilatore è consentito (ma non necessario) per trovare le corrispondenze con i modelli non in ordine e per riutilizzare i risultati dei modelli già corrispondenti per calcolare il risultato della corrispondenza di altri modelli. Tuttavia, è necessario un compilatore per determinare il primo schema lessicale che corrisponde all'espressione e per il quale la clausola di guardia è assente o si valuta a
true. nota finale
Se il punto finale dell'elenco di istruzioni di una sezione switch è raggiungibile, si verifica un errore in fase di compilazione. Questa è chiamata la regola del "no fall through".
Esempio: esempio
switch (i) { case 0: CaseZero(); break; case 1: CaseOne(); break; default: CaseOthers(); break; }è valido perché nessuna sezione switch ha un punto finale raggiungibile. A differenza di C e C++, l'esecuzione di una sezione switch non è consentita per "passare" alla sezione switch successiva e l'esempio
switch (i) { case 0: CaseZero(); case 1: CaseZeroOrOne(); default: CaseAny(); }genera un errore in fase di compilazione. Quando l'esecuzione di una sezione switch deve essere seguita dall'esecuzione di un'altra sezione switch, si deve usare un'istruzione esplicita:
goto caseogoto default.switch (i) { case 0: CaseZero(); goto case 1; case 1: CaseZeroOrOne(); goto default; default: CaseAny(); break; }esempio finale
In un switch_section sono consentite più etichette.
Esempio: esempio
switch (i) { case 0: CaseZero(); break; case 1: CaseOne(); break; case 2: default: CaseTwo(); break; }è valido. L'esempio non viola la regola "no fall through" perché le etichette
case 2:edefault:fanno parte della stessa switch_section.esempio finale
Nota: la regola "no fall through" impedisce una classe comune di bug che si verificano in C e C++ quando
breakle istruzioni vengono accidentalmente omesse. Ad esempio, le sezioni dell'istruzioneswitchprecedente possono essere annullate senza influire sul comportamento dell'istruzione :switch (i) { default: CaseAny(); break; case 1: CaseZeroOrOne(); goto default; case 0: CaseZero(); goto case 1; }nota finale
Nota: l'elenco di istruzioni di una sezione switch termina in genere in un'istruzione
break,goto caseogoto default, ma qualsiasi costrutto che esegue il rendering del punto finale dell'elenco di istruzioni non raggiungibile è consentito. Ad esempio, un'istruzionewhilecontrollata dall'espressionetruebooleana è nota per non raggiungere mai il punto finale. Analogamente, un'istruzionethroworeturntrasferisce sempre il controllo altrove e non raggiunge mai il punto finale. Di conseguenza, l'esempio seguente è valido:switch (i) { case 0: while (true) { F(); } case 1: throw new ArgumentException(); case 2: return; }nota finale
Esempio: il tipo di regola di un'istruzione
switchpuò essere il tipostring. Ad esempio:void DoCommand(string command) { switch (command.ToLower()) { case "run": DoRun(); break; case "save": DoSave(); break; case "quit": DoQuit(); break; default: InvalidCommand(command); break; } }esempio finale
Nota: analogamente agli operatori di uguaglianza delle stringhe (§12.14.8), l'istruzione
switchfa distinzione tra maiuscole e minuscole e eseguirà una determinata sezione switch solo se la stringa selector_expression dell'opzione corrisponde esattamente a unacasecostante di etichetta. nota finale
Quando il tipo di regola di un'istruzione switch è string o un tipo valore nullable, il valore null è consentito come costante di case etichetta.
I statement_listdi un switch_block possono contenere istruzioni di dichiarazione (§13.6). L'ambito di una variabile locale o di una costante dichiarata in un blocco switch è il blocco switch.
Un'etichetta switch è raggiungibile se almeno una delle condizioni seguenti è vera:
- Il selector_expression del commutatore è un valore costante e
- l'etichetta è un
caseil cui modello corrisponde (§11.2.1) a quel valore, e la guardia dell'etichetta è assente o non è un'espressione costante con il valore false; o - è un'etichetta
defaulte nessuna sezione switch contiene un'etichetta case il cui modello corrisponde a tale valore e la cui guardia è o assente o un'espressione costante con il valore true.
- l'etichetta è un
- Il selector_expression dell'opzione non è un valore costante e
- l'etichetta è un
casesenza protezione o con la protezione il cui valore non è la costante falso; o - è un'etichetta
defaulte- il set di modelli visualizzati tra i casi dell'istruzione switch che non hanno guardie o hanno guardie il cui valore è la costante true, non è esaustivo (§11,4) per il tipo di controllo del cambio; o
- Il tipo di governo dello switch è un tipo nullable e il set di modelli che appaiono tra i casi dell'istruzione switch che non hanno guardie o hanno guardie il cui valore è la costante true non include un modello che corrisponde al valore
null.
- l'etichetta è un
- L'etichetta switch è referenziata da un'istruzione
goto caseogoto defaultraggiungibile.
L'elenco di istruzioni di una determinata sezione switch è raggiungibile se l'istruzione switch è raggiungibile e la sezione switch contiene un'etichetta switch raggiungibile.
Il punto finale di un'istruzione switch è raggiungibile se l'istruzione switch è raggiungibile e almeno uno dei seguenti è vero:
- L'istruzione
switchcontiene un'istruzione raggiungibilebreakche esce dall'istruzioneswitch. - Nessuna
defaultetichetta è presente o- L'selector_expression del commutatore è un valore non costante e il set di modelli visualizzati tra i casi dell'istruzione switch che non hanno guardie o hanno guardie il cui valore è la costante true, non è esaustivo (§11,4) per il tipo di controllo del commutatore.
- Il selector_expression dell'opzione è un valore non costante di un tipo nullable e non viene visualizzato alcun criterio tra i casi dell'istruzione switch che non hanno guardie o hanno guardie il cui valore è la costante true corrisponde al valore
null. - Il selector_expression dell'opzione è un valore costante e nessuna
caseetichetta senza protezione o la cui protezione è la costante true corrisponde a tale valore.
Esempio: il codice seguente mostra un uso conciso della
whenclausola :static object CreateShape(string shapeDescription) { switch (shapeDescription) { case "circle": return new Circle(2); … case var o when string.IsNullOrWhiteSpace(o): return null; default: return "invalid shape description"; } }Il caso var corrisponde a
null, alla stringa vuota o a qualsiasi stringa che contiene solo spazi vuoti. esempio finale
13.9 Istruzioni di iterazione
13.9.1 Generale
Le istruzioni di iterazione eseguono ripetutamente un'istruzione incorporata.
iteration_statement
: while_statement
| do_statement
| for_statement
| foreach_statement
;
13.9.2 Istruzione while
L'istruzione while esegue in modo condizionale un'istruzione incorporata zero o più volte.
while_statement
: 'while' '(' boolean_expression ')' embedded_statement
;
Un'istruzione while viene eseguita come segue:
- Il boolean_expression (§12.26) viene valutato.
- Se l'espressione booleana restituisce
true, il controllo viene trasferito all'istruzione incorporata. Quando e se il controllo raggiunge il punto finale dell'istruzione incorporata (possibilmente dall'esecuzione di un'istruzionecontinue), il controllo viene trasferito all'inizio dell'istruzionewhile. - Se l'espressione booleana restituisce
false, il controllo viene trasferito al punto finale dell'istruzionewhile.
All'interno dell'istruzione incorporata di un'istruzionewhile, è possibile utilizzare un'istruzione break (§13.10.2) per trasferire il controllo al punto finale dell'istruzione (terminando così l'iterazione dell'istruzione incorporata) e un'istruzione while (continue) può essere usata per trasferire il controllo al punto finale dell'istruzione incorporata (eseguendo così un'altra iterazione dell'istruzionewhile).
L'istruzione incorporata di un'istruzione while è raggiungibile se l'istruzione while è raggiungibile e l'espressione booleana non ha il valore costante false.
Il punto finale di un'istruzione while è raggiungibile se almeno una delle condizioni seguenti è vera:
- L'istruzione
whilecontiene un'istruzione raggiungibilebreakche esce dall'istruzionewhile. - L'istruzione
whileè raggiungibile e l'espressione booleana non ha il valoretruecostante .
13.9.3 Istruzione do
L'istruzione do esegue in modo condizionale un'istruzione incorporata una o più volte.
do_statement
: 'do' embedded_statement 'while' '(' boolean_expression ')' ';'
;
Un'istruzione do viene eseguita come segue:
- Il controllo viene trasferito all'istruzione incorporata.
- Quando e se il controllo raggiunge il punto finale dell'istruzione incorporata (possibilmente dall'esecuzione di un'istruzione
continue), viene valutato il boolean_expression (§12.26). Se l'espressione booleana restituiscetrue, il controllo viene trasferito all'inizio dell'istruzionedo. In caso contrario, il controllo viene trasferito al punto finale dell'istruzionedo.
All'interno dell'istruzione incorporata di un'istruzionedo, è possibile utilizzare un'istruzione break (§13.10.2) per trasferire il controllo al punto finale dell'istruzione (terminando così l'iterazione dell'istruzione incorporata) e un'istruzione do (continue) può essere usata per trasferire il controllo al punto finale dell'istruzione incorporata (eseguendo così un'altra iterazione dell'istruzionedo).
L'istruzione incorporata di un'istruzione do è raggiungibile se l'istruzione do è raggiungibile.
Il punto finale di un'istruzione do è raggiungibile se almeno una delle condizioni seguenti è vera:
- L'istruzione
docontiene un'istruzione raggiungibilebreakche esce dall'istruzionedo. - Il punto finale dell'istruzione incorporata è raggiungibile e l'espressione booleana non ha il valore
truecostante .
13.9.4 Istruzione for
L'istruzione for valuta una sequenza di espressioni di inizializzazione e quindi, mentre una condizione è true, esegue ripetutamente un'istruzione incorporata e valuta una sequenza di espressioni di iterazione.
for_statement
: 'for' '(' for_initializer? ';' for_condition? ';' for_iterator? ')'
embedded_statement
;
for_initializer
: local_variable_declaration
| statement_expression_list
;
for_condition
: boolean_expression
;
for_iterator
: statement_expression_list
;
statement_expression_list
: statement_expression (',' statement_expression)*
;
Il for_initializer, se presente, è costituito da un local_variable_declaration (§13.6.2) o da un elenco di statement_expression(§13.7) separati da virgole. L'ambito di una variabile locale dichiarata da un for_initializer è il for_initializer, for_condition, for_iterator e embedded_statement.
Il for_condition, se presente, sarà un boolean_expression (§12.26).
Il for_iterator, se presente, è costituito da un elenco di statement_expression(§13.7) separati da virgole.
Un'istruzione for viene eseguita come segue:
- Se è presente un for_initializer , gli inizializzatori di variabili o le espressioni di istruzione vengono eseguiti nell'ordine in cui vengono scritti. Questo passaggio viene eseguito una sola volta.
- Se è presente un for_condition , viene valutato.
- Se il for_condition non è presente o se la valutazione restituisce
true, il controllo viene trasferito all'istruzione incorporata. Quando e se il controllo raggiunge il punto finale dell'istruzione incorporata (possibilmente dall'esecuzione di un'istruzionecontinue), le espressioni del for_iterator, se presenti, vengono valutate in sequenza e viene eseguita un'altra iterazione, a partire dalla valutazione del for_condition nel passaggio precedente. - Se il for_condition è presente e la valutazione restituisce
false, il controllo viene trasferito al punto finale dell'istruzionefor.
All'interno dell'istruzione incorporata di un'istruzionefor, è possibile utilizzare un'istruzione break (§13.10.2) per trasferire il controllo al punto finale dell'istruzione (terminando così l'iterazione dell'istruzione incorporata) e una istruzione for può essere utilizzata per trasferire il controllo al punto finale dell'istruzione incorporata (eseguendo il continue e svolgendo un'altra iterazione dell'istruzione, partendo dalla for_condition).
L'istruzione incorporata di un'istruzione for è raggiungibile se una delle condizioni seguenti è vera:
- L'istruzione
forè raggiungibile e non è presente alcuna for_condition . - L'istruzione
forè raggiungibile e un for_condition è presente e non ha il valorefalsecostante .
Il punto finale di un'istruzione for è raggiungibile se almeno una delle condizioni seguenti è vera:
- L'istruzione
forcontiene un'istruzione raggiungibilebreakche esce dall'istruzionefor. - L'istruzione
forè raggiungibile e un for_condition è presente e non ha il valoretruecostante .
13.9.5 Istruzione foreach
13.9.5.1 Generale
L'istruzione foreach enumera gli elementi di una raccolta, eseguendo un'istruzione incorporata per ogni elemento della raccolta.
foreach_statement
: 'await'? 'foreach' '(' ref_kind? local_variable_type identifier
'in' expression ')' embedded_statement
;
Il local_variable_type e l'identificatore di un'istruzione foreach dichiarano la variabile di iterazione dell'istruzione. Se l'identificatore var viene assegnato come local_variable_type e nessun tipo denominato var è nell'ambito, la variabile di iterazione viene considerata una variabile di iterazione tipizzata in modo implicito e il relativo tipo viene considerato il tipo di elemento dell'istruzioneforeach, come specificato di seguito.
Si tratta di un errore in fase di compilazione quando sia await che ref_kind sono presenti in un elemento foreach statement.
Se il foreach_statement contiene entrambi o nessuno dei due ref e readonly, la variabile di iterazione indica una variabile considerata di sola lettura. In caso contrario, se foreach_statement contiene ref senza readonly, la variabile di iterazione indica una variabile che deve essere scrivibile.
La variabile di iterazione corrisponde a una variabile locale con un ambito che si estende sull'istruzione incorporata. Durante l'esecuzione di un'istruzione foreach , la variabile di iterazione rappresenta l'elemento della raccolta per il quale viene attualmente eseguita un'iterazione. Se la variabile di iterazione indica una variabile di sola lettura, si verifica un errore in fase di compilazione se l'istruzione incorporata tenta di modificarla (tramite assegnazione o ++ operatori e -- ) o di passarla come riferimento o parametro di output.
L'elaborazione in fase di compilazione di un'istruzione foreach determina innanzitutto il tipo di raccolta (C), il tipo di enumeratore (E) e il tipo di iterazione (Tref To ref readonly T) dell'espressione.
La determinazione è simile per le versioni sincrone e asincrone. Interfacce diverse con metodi diversi e tipi restituiti distinguono le versioni sincrone e asincrone. Il processo generale procede come segue. I nomi all'interno di '«' e '»sono segnaposto per i nomi effettivi per gli iteratori sincroni e asincroni. I tipi consentiti per «GetEnumerator», «MoveNext», «IEnumerable»T, «IEnumerator»<>T< e qualsiasi altra distinzione sono descritti in > per un'istruzione sincrona e in foreach per un'istruzione asincrona.foreach
- Determinare se il tipo
Xdi espressione ha un metodo «GetEnumerator» appropriato:- Eseguire la ricerca dei membri sul tipo
Xcon identificatore «GetEnumerator» e nessun argomento di tipo. Se la ricerca del membro non produce una corrispondenza o produce ambiguità o produce una corrispondenza che non è un gruppo di metodi, verificare la presenza di un'interfaccia enumerabile come descritto nel passaggio 2. È consigliabile generare un avviso se la ricerca dei membri produce qualsiasi cosa eccetto un gruppo di metodi o nessuna corrispondenza. - Eseguire la risoluzione dell'overload usando il gruppo di metodi risultante e un elenco di argomenti vuoto. Se la risoluzione dell'overload non produce metodi applicabili, genera un'ambiguità o restituisce un singolo metodo migliore, ma tale metodo è statico o non pubblico, verificare la presenza di un'interfaccia enumerabile come descritto di seguito. È consigliato emettere un avviso se la risoluzione del sovraccarico produce qualcosa di diverso da un metodo di istanza pubblica chiaro o se non ci sono metodi applicabili.
- Se il tipo
Erestituito del metodo «GetEnumerator» non è una classe, uno struct o un tipo di interfaccia, generare un errore e non eseguire ulteriori passaggi. - Eseguire la ricerca
Edei membri con l'identificatoreCurrente senza argomenti di tipo. Se la ricerca del membro non produce corrispondenze, il risultato è un errore o il risultato è qualsiasi cosa tranne una proprietà dell'istanza pubblica che consente la lettura, generare un errore e non eseguire ulteriori passaggi. - Eseguire la ricerca
Edei membri con l'identificatore «MoveNext» e senza argomenti di tipo. Se la ricerca del membro non produce alcuna corrispondenza, il risultato è un errore o il risultato è qualsiasi elemento ad eccezione di un gruppo di metodi, generare un errore e non eseguire ulteriori passaggi. - Eseguire la risoluzione dell'overload nel gruppo di metodi con un elenco di argomenti vuoto. Se la risoluzione dell'overload restituisce: nessun metodo applicabile; ambiguità; o un singolo metodo migliore, ma tale metodo è statico o non pubblico oppure il tipo restituito non è un tipo restituito consentito; generare quindi un errore e non eseguire ulteriori passaggi.
- Il tipo di raccolta è
X, il tipo di enumeratore èEe il tipo di iterazione è il tipo dellaCurrentproprietà .
- Eseguire la ricerca dei membri sul tipo
- In caso contrario, verificare la presenza di un'interfaccia enumerabile:
- Se tra tutti i tipi
Tᵢper i quali è presente una conversione implicita daXa «IEnumerable»<Ti>, esiste un tipoTunivoco cheTnondynamicsia e per tutti gli altriTᵢesiste una conversione implicita da «IEnumerable»T< a «IEnumerable»>Ti<, il tipo di raccolta è l'interfaccia «IEnumerable»>T<, il tipo di enumeratore è l'interfaccia «IEnumerator»><T>, e il tipo di iterazione èT. - In caso contrario, se è presente più di un tipo di questo tipo
T, generare un errore e non eseguire altri passaggi.
- Se tra tutti i tipi
Nota: se l'espressione ha il valore
null, viene generata un'eccezioneSystem.NullReferenceExceptionin fase di esecuzione. nota finale
Un'implementazione può implementare un determinato foreach_statement in modo diverso; ad esempio, per motivi di prestazioni, purché il comportamento sia coerente con le espansioni descritte in §13.9.5.2 e §13.9.5.3.
13.9.5.2 Foreach sincrono
Una parola chiave sincrona foreach non include la await parola chiave prima della foreach parola chiave . La determinazione del tipo di raccolta, del tipo di enumerazione e del tipo di iterazione procede come descritto in §13.9.5.1, dove:
- «GetEnumerator» è un
GetEnumeratormetodo. - «MoveNext» è un
MoveNextmetodo con unbooltipo restituito. - «IEnumerable»<T> è l'interfaccia
System.Collections.Generic.IEnumerable<T>. - «IEnumerator»<T> è l'interfaccia
System.Collections.Generic.IEnumerator<T>.
Inoltre, vengono apportate le seguenti modifiche ai passaggi descritti in §13.9.5.1:
Prima del processo descritto in §13.9.5.1, vengono eseguiti i passaggi seguenti:
- Se il tipo di
Xè un tipo di matrice, è presente una conversione di riferimento implicita daXall'interfacciaIEnumerable<T>in cuiTè il tipo di elemento della matriceX(§17.2.3). - Se il tipo
Xdi espressione èdynamicpresente una conversione implicita dall'espressione all'interfacciaIEnumerable(§10.2.10). Il tipo di raccolta è l'interfacciaIEnumerablee il tipo di enumeratore è l'interfacciaIEnumerator. Se l'identificatorevarviene assegnato come local_variable_type , il tipo di iterazione èdynamic, in caso contrario èobject.
Se il processo in §13.9.5.1 viene completato senza produrre un singolo tipo di raccolta, un tipo di enumeratore e un tipo di iterazione, vengono eseguiti i passaggi seguenti:
- Se è presente una conversione implicita da
Xall'interfacciaSystem.Collections.IEnumerable, il tipo di raccolta è questa interfaccia, il tipo di enumeratore è l'interfacciaSystem.Collections.IEnumeratore il tipo di iterazione èobject. - In caso contrario, viene generato un errore e non vengono eseguiti altri passaggi.
Un'istruzione foreach del form
foreach (V v in x) «embedded_statement»
è quindi equivalente a:
{
E e = ((C)(x)).GetEnumerator();
try
{
while (e.MoveNext())
{
V v = (V)(T)e.Current;
«embedded_statement»
}
}
finally
{
... // Dispose e
}
}
La variabile e non è visibile o accessibile all'espressione x o all'istruzione incorporata o a qualsiasi altro codice sorgente del programma. La variabile v è di sola lettura nell'istruzione incorporata. Se non è presente una conversione esplicita (§10.3) da T (tipo di iterazione) a V (il local_variable_type nell'istruzione foreach ), viene generato un errore e non vengono eseguiti altri passaggi.
Quando la variabile di iterazione è una variabile di riferimento (§9.7), un'istruzione foreach del modulo
foreach (ref V v in x) «embedded_statement»
è quindi equivalente a:
{
E e = ((C)(x)).GetEnumerator();
try
{
while (e.MoveNext())
{
ref V v = ref e.Current;
«embedded_statement»
}
}
finally
{
... // Dispose e
}
}
La variabile e non è visibile o accessibile all'espressione x o all'istruzione incorporata o a qualsiasi altro codice sorgente del programma. La variabile v di riferimento è di lettura/scrittura nell'istruzione incorporata, ma v non deve essere riassegnata (§12.23.3). Se non è presente una conversione di identità (§10.2.2) da T (tipo di iterazione) a V (il local_variable_type nell'istruzione foreach ), viene generato un errore e non vengono eseguiti altri passaggi.
Un'istruzione foreach del form foreach (ref readonly V v in x) «embedded_statement» ha una forma equivalente simile, ma la variabile v di riferimento si trova ref readonly nell'istruzione incorporata e pertanto non può essere riassegnata o riassegnata.
La posizione all'interno v del while ciclo è importante per la modalità di acquisizione (§12.21.6.2) da qualsiasi funzione anonima che si verifica nel embedded_statement.
Esempio:
int[] values = { 7, 9, 13 }; Action f = null; foreach (var value in values) { if (f == null) { f = () => Console.WriteLine("First value: " + value); } } f();Se
vnel formato espanso fosse dichiarato all'esterno delwhileciclo, verrebbe condiviso tra tutte le iterazioni e il relativo valore dopo ilforciclo sarebbe il valore finale,13, che è ciò che la chiamata afstamperebbe. Poiché ogni iterazione ha invece una propria variabilev, quella acquisita dafnella prima iterazione continuerà a contenere il valore7, ovvero ciò che verrà stampato. Si noti che nelle versioni precedenti di C#,vera dichiarato all'esterno del ciclowhile.esempio finale
Il corpo del finally blocco viene costruito in base ai passaggi seguenti:
Se è presente una conversione implicita da
Eall'interfacciaSystem.IDisposable,Se
Eè un tipo di valore non nullable, lafinallyclausola viene espansa fino all'equivalente semantico di:finally { ((System.IDisposable)e).Dispose(); }In caso contrario, la
finallyclausola viene espansa all'equivalente semantico di:finally { System.IDisposable d = e as System.IDisposable; if (d != null) { d.Dispose(); } }Ad eccezione del fatto che se
Eè un tipo di valore o un parametro di tipo istanziato come un tipo di valore, la conversione dieaSystem.IDisposablenon deve causare il boxing.
In caso contrario, se
Eè un tipo sealed, lafinallyclausola viene espansa in un blocco vuoto:finally {}In caso contrario, la
finallyclausola viene espansa in:finally { System.IDisposable d = e as System.IDisposable; if (d != null) { d.Dispose(); } }
La variabile d locale non è visibile o accessibile a qualsiasi codice utente. In particolare, non è in conflitto con altre variabili il cui ambito include il finally blocco.
L'ordine in cui foreach attraversa gli elementi di una matrice è il seguente: Per gli elementi di matrici unidimensionali vengono attraversati in ordine di indice crescente, a partire dall'indice 0 e terminando con l'indice Length – 1. Per le matrici multidimensionali, gli elementi vengono attraversati in modo che gli indici della dimensione più a destra vengano prima aumentati, quindi la dimensione sinistra successiva e così via a sinistra.
Esempio: l'esempio seguente stampa ogni valore in una matrice bidimensionale, in ordine di elemento:
class Test { static void Main() { double[,] values = { {1.2, 2.3, 3.4, 4.5}, {5.6, 6.7, 7.8, 8.9} }; foreach (double elementValue in values) { Console.Write($"{elementValue} "); } Console.WriteLine(); } }L'output prodotto è il seguente:
1.2 2.3 3.4 4.5 5.6 6.7 7.8 8.9esempio finale
Esempio: nell'esempio seguente
int[] numbers = { 1, 3, 5, 7, 9 }; foreach (var n in numbers) { Console.WriteLine(n); }il tipo di
nviene dedotto comeint, il tipo di iterazione dinumbers.esempio finale
13.9.5.3 await foreach
Un foreach asincrono usa la await foreach sintassi . La determinazione del tipo di raccolta, del tipo di enumerazione e del tipo di iterazione procede come descritto in §13.9.5.1, dove:
- «GetEnumerator» è un
GetEnumeratorAsyncmetodo con un tipo restituito awaitable (§12.9.9.2). - «MoveNext» è un
MoveNextAsyncmetodo con tipo restituito awaitable (§12.9.9.2) in cui il await_expression è classificato come (bool§12.9.9.3). - «IEnumerable»<T> è l'interfaccia
System.Collections.Generic.IAsyncEnumerable<T>. - «IEnumerator»<T> è l'interfaccia
System.Collections.Generic.IAsyncEnumerator<T>.
Si tratta di un errore per il tipo di iterazione di un'istruzione await foreach come variabile di riferimento (§9.7).
Un'istruzione await foreach della forma
await foreach (T item in enumerable) «embedded_statement»
è semanticamente equivalente a:
var enumerator = enumerable.GetAsyncEnumerator();
try
{
while (await enumerator.MoveNextAsync())
{
T item = enumerator.Current;
«embedded_statement»
}
}
finally
{
// dispose of enumerator as described later in this clause.
}
Nel caso in cui l'espressione enumerable rappresenta un'espressione di chiamata al metodo e uno dei parametri è contrassegnato con (EnumeratorCancellationAttribute§23.5.8) viene CancellationToken passato al GetAsyncEnumerator metodo . Altri metodi di libreria possono richiedere che un CancellationToken oggetto venga passato a GetAsyncEnumerator. Quando questi metodi fanno parte dell'espressione enumerable, i token devono essere combinati in un singolo token come se fosse CreateLinkedTokenSource e la relativa Token proprietà.
Il corpo del finally blocco viene costruito in base ai passaggi seguenti:
Se
Edispone di un metodo accessibileDisposeAsync()in cui il tipo restituito è awaitable (§12.9.9.2), lafinallyclausola viene espansa fino all'equivalente semantico di:finally { await e.DisposeAsync(); }In caso contrario, se è presente una conversione implicita da
Eall'interfacciaSystem.IAsyncDisposableedEè un tipo di valore non nullable, lafinallyclausola viene espansa nell'equivalente semantico di:finally { await ((System.IAsyncDisposable)e).DisposeAsync(); }Ad eccezione del fatto che se
Eè un tipo di valore o un parametro di tipo istanziato come un tipo di valore, la conversione dieaSystem.IAsyncDisposablenon deve causare il boxing.In caso contrario, se
Eè un tipo e ha unref structmetodo accessibileDispose(), lafinallyclausola viene espansa all'equivalente semantico di:finally { e.Dispose(); }In caso contrario, se
Eè un tipo sealed, lafinallyclausola viene espansa in un blocco vuoto:finally {}In caso contrario, la
finallyclausola viene espansa in:finally { System.IAsyncDisposable d = e as System.IAsyncDisposable; if (d != null) { await d.DisposeAsync(); } }
La variabile d locale non è visibile o accessibile a qualsiasi codice utente. In particolare, non è in conflitto con altre variabili il cui ambito include il finally blocco.
Nota:
await foreachnon è necessario eliminareein modo sincrono un meccanismo dispose asincrono se non è disponibile un meccanismo di eliminazione asincrona. nota finale
13.10 Istruzioni di salto
13.10.1 Generale
Le istruzioni di salto trasferiscono in modo incondizionato il controllo.
jump_statement
: break_statement
| continue_statement
| goto_statement
| return_statement
| throw_statement
;
La posizione in cui un'istruzione jump trasferisce il controllo viene chiamata destinazione dell'istruzione jump.
Quando si verifica un'istruzione jump all'interno di un blocco e la destinazione di tale istruzione jump si trova all'esterno di tale blocco, l'istruzione jump viene detta uscita dal blocco. Mentre un'istruzione jump può trasferire il controllo da un blocco, non può mai trasferire il controllo in un blocco.
L'esecuzione di istruzioni jump è complicata dalla presenza di istruzioni intermedie try . In assenza di tali try istruzioni, un'istruzione jump trasferisce in modo incondizionato il controllo dall'istruzione jump alla destinazione. In presenza di tali istruzioni intermedie try , l'esecuzione è più complessa. Se l'istruzione jump esce da uno o più blocchi try con blocchi finally associati, il controllo viene inizialmente trasferito al blocco finally dell'istruzione try più interna. Quando e se il controllo raggiunge il punto finale di un finally blocco, il controllo viene trasferito al finally blocco dell'istruzione di inclusione try successiva. Questo processo viene ripetuto fino a quando non vengono eseguiti i finally blocchi di tutte le istruzioni intermedie try .
Esempio: nel codice seguente
class Test { static void Main() { while (true) { try { try { Console.WriteLine("Before break"); break; } finally { Console.WriteLine("Innermost finally block"); } } finally { Console.WriteLine("Outermost finally block"); } } Console.WriteLine("After break"); } }i blocchi associati a due istruzioni
finallyvengono eseguiti prima che il controllo venga trasferito alla destinazione dell'istruzionetrydi salto. L'output prodotto è il seguente:Before break Innermost finally block Outermost finally block After breakesempio finale
13.10.2 Istruzione break
L'istruzione break esce dall'istruzione switch, while, do, for o foreach più vicina.
break_statement
: 'break' ';'
;
La destinazione di un'istruzione break è il punto finale dell'istruzione più vicina tra switch, while, do, for o foreach che la racchiude. Se un'istruzione break non è racchiusa da un'istruzione switch, while, dofor, o foreach , si verifica un errore in fase di compilazione.
Quando istruzioni multiple come switch, while, do, for o foreach sono annidate l'una dentro l'altra, un'istruzione break si applica solo all'istruzione più interna. Per trasferire il controllo tra più livelli di annidamento, verrà utilizzata un'istruzione goto (§13.10.4).
Un'istruzione break non può uscire da un finally blocco (§13.11). Quando si verifica un'istruzione break all'interno di un finally blocco, la destinazione dell'istruzione break deve trovarsi all'interno dello stesso finally blocco; in caso contrario, si verifica un errore in fase di compilazione.
Un'istruzione break viene eseguita come segue:
- Se l'istruzione
break, esce da uno o più blocchitryche hanno blocchi associatifinally, il controllo viene inizialmente trasferito al bloccofinallydell'istruzionetrypiù interna. Quando e se il controllo raggiunge il punto finale di unfinallyblocco, il controllo viene trasferito alfinallyblocco dell'istruzione di inclusionetrysuccessiva. Questo processo viene ripetuto fino a quando non vengono eseguiti ifinallyblocchi di tutte le istruzioni intermedietry. - Il controllo viene trasferito alla destinazione dell'istruzione
break.
Poiché un'istruzione break trasferisce in modo incondizionato il controllo altrove, il punto finale di un'istruzione break non è mai raggiungibile.
13.10.3 Istruzione continue
L'istruzione continue avvia una nuova iterazione dell'istruzione while, do, for o foreach più vicina.
continue_statement
: 'continue' ';'
;
La destinazione di un'istruzione continue è il punto finale dell'istruzione incorporata della dichiarazione contenitore più vicina tra while, do, for o foreach. Se un'istruzione continue non è racchiusa da un'istruzione while, do, foro foreach , si verifica un errore in fase di compilazione.
Quando più whileistruzioni , do, foro foreach sono annidate tra loro, un'istruzione continue si applica solo all'istruzione più interna. Per trasferire il controllo tra più livelli di annidamento, verrà utilizzata un'istruzione goto (§13.10.4).
Un'istruzione continue non può uscire da un finally blocco (§13.11). Quando si verifica un'istruzione continue all'interno di un finally blocco, la destinazione dell'istruzione continue deve trovarsi all'interno dello stesso finally blocco; in caso contrario, si verifica un errore in fase di compilazione.
Un'istruzione continue viene eseguita come segue:
- Se l'istruzione
continue, esce da uno o più blocchitryche hanno blocchi associatifinally, il controllo viene inizialmente trasferito al bloccofinallydell'istruzionetrypiù interna. Quando e se il controllo raggiunge il punto finale di unfinallyblocco, il controllo viene trasferito alfinallyblocco dell'istruzione di inclusionetrysuccessiva. Questo processo viene ripetuto fino a quando non vengono eseguiti ifinallyblocchi di tutte le istruzioni intermedietry. - Il controllo viene trasferito alla destinazione dell'istruzione
continue.
Poiché un'istruzione continue trasferisce in modo incondizionato il controllo altrove, il punto finale di un'istruzione continue non è mai raggiungibile.
13.10.4 Dichiarazione goto
L'istruzione goto trasferisce il controllo a un'istruzione contrassegnata da un'etichetta.
goto_statement
: 'goto' identifier ';'
| 'goto' 'case' constant_expression ';'
| 'goto' 'default' ';'
;
La destinazione di un'istruzione gotoidentifier è l'istruzione etichettata con l'etichetta specificata. Se un'etichetta con il nome specificato non esiste nel membro della funzione corrente o se l'istruzione goto non rientra nell'ambito dell'etichetta, si verifica un errore in fase di compilazione.
Nota: questa regola consente l'uso di un'istruzione
gotoper trasferire il controllo da un ambito annidato, ma non in un ambito annidato. Nell'esempioclass Test { static void Main(string[] args) { string[,] table = { {"Red", "Blue", "Green"}, {"Monday", "Wednesday", "Friday"} }; foreach (string str in args) { int row, colm; for (row = 0; row <= 1; ++row) { for (colm = 0; colm <= 2; ++colm) { if (str == table[row,colm]) { goto done; } } } Console.WriteLine($"{str} not found"); continue; done: Console.WriteLine($"Found {str} at [{row}][{colm}]"); } } }un'istruzione
gotoviene usata per trasferire il controllo da un ambito annidato.nota finale
La destinazione di un'istruzione goto case è l'elenco di istruzioni nell'istruzione di inclusione switch immediata (§13.8.3) che contiene un'etichetta case con un criterio costante del valore costante specificato e nessuna protezione. Se l'istruzione goto case non è racchiusa da un'istruzione switch , se l'istruzione contenitore più switch vicina non contiene tale case, o se il constant_expression non è convertibile in modo implicito (§10.2) nel tipo di controllo dell'istruzione contenitore switch più vicina, si verifica un errore in fase di compilazione.
La destinazione di un'istruzione goto default è l'elenco di istruzioni nell'istruzione di inclusione switch immediata (§13.8.3), che contiene un'etichetta default . Se l'istruzione goto default non è racchiusa da un'istruzione switch o se l'istruzione contenitore switch più vicina non contiene un'etichetta, si verifica un default errore in fase di compilazione.
Un'istruzione goto non può uscire da un finally blocco (§13.11). Quando si verifica un'istruzione goto all'interno di un finally blocco, la destinazione dell'istruzione goto deve trovarsi nello stesso finally blocco o in caso contrario si verifica un errore in fase di compilazione.
Un'istruzione goto viene eseguita come segue:
- Se l'istruzione
goto, esce da uno o più blocchitryche hanno blocchi associatifinally, il controllo viene inizialmente trasferito al bloccofinallydell'istruzionetrypiù interna. Quando e se il controllo raggiunge il punto finale di unfinallyblocco, il controllo viene trasferito alfinallyblocco dell'istruzione di inclusionetrysuccessiva. Questo processo viene ripetuto fino a quando non vengono eseguiti ifinallyblocchi di tutte le istruzioni intermedietry. - Il controllo viene trasferito alla destinazione dell'istruzione
goto.
Poiché un'istruzione goto trasferisce in modo incondizionato il controllo altrove, il punto finale di un'istruzione goto non è mai raggiungibile.
13.10.5 Istruzione return
L'istruzione return restituisce il controllo al chiamante corrente del membro della funzione in cui viene visualizzata l'istruzione return, restituendo facoltativamente un valore o un variable_reference (§9,5).
return_statement
: 'return' ';'
| 'return' expression ';'
| 'return' 'ref' variable_reference ';'
;
Un return_statement senza espressione viene chiamato return-no-value; uno che contiene ref è chiamato return-by-ref; e uno contenente solo espressione è denominato return-by-value.
È un errore di compilazione utilizzare un metodo senza valore di ritorno quando è dichiarato come return-by-value o returns-by-ref (§15.6.1).
È un errore in fase di compilazione utilizzare un ritorno-per-riferimento da un metodo dichiarato come non-restituisce-valore o restituisce-per-valore.
Si verifica un errore in fase di compilazione quando si usa una restituzione per valore da un metodo dichiarato come non restituisce valore o restituzione per riferimento.
Si verifica un errore di compilazione quando si usa un return-by-ref se expression non è un variable_reference o se è un riferimento a una variabile il cui ref-safe-context non è caller-context (§9.7.2).
Si verifica un errore in fase di compilazione quando si utilizza un return-by-ref da un metodo dichiarato con il method_modifierasync.
Un membro della funzione viene detto di calcolare un valore se si tratta di un metodo che restituisce per valore (§15.6.11), un accessor get che restituisce per valore di una proprietà o un indicizzatore o di un operatore definito dall'utente. I membri della funzione che non restituiscono un valore non calcolano un valore e sono metodi con il tipo void di ritorno effettivo, i modificatori di accesso set di proprietà e indicizzatori, e i modificatori di accesso aggiungi e rimuovi di eventi, i costruttori di istanza, i costruttori statici e i finalizzatori. I membri della funzione che sono returns-by-ref non calcolano un valore.
Per un ritorno tramite valore, deve esistere una conversione implicita (§10.2) dal tipo di espressione al tipo restituito effettivo (§15.6.11) del membro della funzione contenitore. Per una restituzione per riferimento, deve esistere una conversione di identità (§10.2.2) tra il tipo di espressione e il tipo restituito effettivo del membro della funzione contenitore.
return Le istruzioni possono essere usate anche nel corpo delle espressioni di funzione anonime (§12.21) e partecipare alla determinazione delle conversioni esistenti per tali funzioni (§10.7.1).
È un errore di compilazione se un'istruzione return appare in un blocco finally (§13.11).
Un'istruzione return viene eseguita come segue:
- In un ritorno per valore, l'espressione viene valutata e il suo valore viene convertito nel tipo di ritorno effettivo della funzione che lo contiene tramite una conversione implicita. Il risultato della conversione diventa il valore del risultato prodotto dalla funzione . Per una restituzione per riferimento, l'espressione viene valutata e il risultato deve essere classificato come variabile. Se il metodo di inclusione return-by-ref include
readonly, la variabile risultante è di sola lettura. - Se l'istruzione
returnè racchiusa da uno o piùtrycatchblocchi con blocchi associatifinally, il controllo viene inizialmente trasferito alfinallyblocco dell'istruzione piùtryinterna. Quando e se il controllo raggiunge il punto finale di unfinallyblocco, il controllo viene trasferito alfinallyblocco dell'istruzione di inclusionetrysuccessiva. Questo processo viene ripetuto fino a quando non vengono eseguiti ifinallyblocchi di tutte le istruzioni che racchiudonotry. - Se la funzione contenitore non è una funzione asincrona, il controllo viene restituito al chiamante della funzione contenitore insieme al valore del risultato, se presente.
- Se la funzione contenitore è una funzione asincrona, il controllo viene restituito al chiamante corrente e il valore del risultato, se presente, viene registrato nell'attività restituita come descritto in (§15.14.3).
Poiché un'istruzione return trasferisce in modo incondizionato il controllo altrove, il punto finale di un'istruzione return non è mai raggiungibile.
13.10.6 Istruzione throw
L'istruzione throw genera un'eccezione.
throw_statement
: 'throw' expression? ';'
;
Un'istruzione throw con un'espressione genera un'eccezione generata dalla valutazione dell'espressione. L'espressione deve essere convertibile in modo implicito in System.Exceptione il risultato della valutazione dell'espressione viene convertito in System.Exception prima di essere generato. Se il risultato della conversione è null, viene invece lanciato un System.NullReferenceException.
Un'istruzione throw senza espressione può essere usata solo in un catch blocco, nel qual caso tale istruzione genera nuovamente l'eccezione attualmente gestita da tale catch blocco.
Poiché un'istruzione throw trasferisce in modo incondizionato il controllo altrove, il punto finale di un'istruzione throw non è mai raggiungibile.
Quando viene generata un'eccezione, il controllo viene trasferito alla prima catch clausola in un'istruzione di inclusione try in grado di gestire l'eccezione. Il processo che si verifica dal punto dell'eccezione generata al punto di trasferimento del controllo a un gestore di eccezioni appropriato è noto come propagazione delle eccezioni. La propagazione di un'eccezione consiste nella valutazione ripetuta dei passaggi seguenti fino a quando non viene trovata una catch clausola corrispondente all'eccezione. In questa descrizione, il punto throw è inizialmente la posizione in cui viene generata l'eccezione. Questo comportamento è specificato in (§22.4).
Nel membro della funzione corrente viene esaminata ogni
tryistruzione che racchiude il punto throw. Per ogni istruzioneS, a partire dall'istruzione piùtryinterna e terminando con l'istruzione più esternatry, vengono valutati i passaggi seguenti:- Se il blocco di
tryracchiude il punto diSgenerazione e seSha una o piùcatchclausole, le clausolecatchvengono esaminate in ordine di apparizione per individuare un gestore appropriato per l'eccezione. La primacatchclausola che specifica un tipo di eccezione (To un parametro di tipo che in fase di esecuzione denota un tipo di eccezione) tale che il tipo di runtimeTdiEderiva daTè considerato una corrispondenza. Se la clausola contiene un filtro eccezioni, l'oggetto eccezione viene assegnato alla variabile di eccezione e viene valutato il filtro eccezioni. Quando unacatchclausola contiene un filtro di eccezione, talecatchclausola viene considerata una corrispondenza se il filtro eccezioni restituiscetrue. Una clausola generalecatch(§13.11) è considerata una corrispondenza per qualsiasi tipo di eccezione. Se si trova una clausola corrispondentecatch, la propagazione dell'eccezione viene completata trasferendo il controllo al blocco di talecatchclausola. - In caso contrario, se il
tryblocco o uncatchblocco di racchiude il punto throwSe seSha unfinallyblocco, il controllo viene trasferito alfinallyblocco. Se ilfinallyblocco genera un'altra eccezione, l'elaborazione dell'eccezione corrente viene terminata. In caso contrario, quando il controllo raggiunge il punto finale delfinallyblocco, l'elaborazione dell'eccezione corrente viene continuata.
- Se il blocco di
Se un gestore eccezioni non si trova nella chiamata alla funzione corrente, la chiamata alla funzione viene terminata e si verifica una delle operazioni seguenti:
Se la funzione corrente non è asincrona, i passaggi precedenti vengono ripetuti per il chiamante della funzione con un punto throw corrispondente all'istruzione da cui è stato richiamato il membro della funzione.
Se la funzione corrente è asincrona e restituisce attività, l'eccezione viene registrata nell'attività restituita, che viene inserita in uno stato di errore o annullato, come descritto in §15.14.3.
Se la funzione corrente è asincrona e
void-ritornante, il contesto di sincronizzazione del thread corrente viene informato come descritto in §15.14.4.
Se l'elaborazione dell'eccezione termina tutte le chiamate ai membri della funzione nel thread corrente, a indicare che il thread non dispone di alcun gestore per l'eccezione, il thread viene terminato. L'impatto di tale terminazione è definito dall'implementazione.
13.11 Istruzione try
L'istruzione try fornisce un meccanismo per rilevare le eccezioni che si verificano durante l'esecuzione di un blocco. Inoltre, l'istruzione try consente di specificare un blocco di codice che viene sempre eseguito quando il controllo lascia l'istruzione try .
try_statement
: 'try' block catch_clauses
| 'try' block catch_clauses? finally_clause
;
catch_clauses
: specific_catch_clause+
| specific_catch_clause* general_catch_clause
;
specific_catch_clause
: 'catch' exception_specifier exception_filter? block
| 'catch' exception_filter block
;
exception_specifier
: '(' type identifier? ')'
;
exception_filter
: 'when' '(' boolean_expression ')'
;
general_catch_clause
: 'catch' block
;
finally_clause
: 'finally' block
;
Un try_statement è costituito dalla parola chiave try seguita da un blocco, quindi da zero o più catch_clauses, quindi da un finally_clause facoltativo. Deve essere presente almeno un catch_clause o un finally_clause.
In un exception_specifier il tipo o la relativa classe base effettiva, se si tratta di un type_parameter, deve essere System.Exception o un tipo che ne deriva.
Quando una catch clausola specifica sia un class_type che un identificatore, viene dichiarata una variabile di eccezione del nome e del tipo specificati. La variabile di eccezione viene introdotta nello spazio di dichiarazione del specific_catch_clause (§7.3). Durante l'esecuzione del blocco exception_filter e catch, la variabile di eccezione rappresenta l'eccezione attualmente gestita. Ai fini del controllo dell'assegnazione definita, la variabile di eccezione viene considerata sicuramente assegnata nell'intero ambito.
A meno che una catch clausola non includa un nome di variabile di eccezione, non è possibile accedere all'oggetto eccezione nel filtro e catch nel blocco.
Una catch clausola che specifica né un tipo di eccezione né un nome di variabile di eccezione è detta clausola generale catch . Una dichiarazione try può avere una sola clausola generale catch e, se presente, sarà l'ultima clausola catch.
Nota: alcuni linguaggi di programmazione potrebbero supportare eccezioni che non sono rappresentabili come oggetto derivato da
System.Exception, anche se tali eccezioni non possono mai essere generate dal codice C#. Una clausola generalecatchpuò essere utilizzata per intercettare tali eccezioni. Pertanto, una clausola generalecatchè semanticamente diversa da quella che specifica il tipoSystem.Exception, in quanto la prima potrebbe anche intercettare le eccezioni da altre lingue. nota finale
Per individuare un gestore per un'eccezione, catch le clausole vengono esaminate in ordine lessicale. Se una catch clausola specifica un tipo ma nessun filtro di eccezione, si tratta di un errore in fase di compilazione per una clausola successiva catch della stessa try istruzione per specificare un tipo uguale a o derivato da tale tipo.
Nota: senza questa restrizione, sarebbe possibile scrivere clausole non raggiungibili
catch. nota finale
All'interno di un catch blocco, un'istruzione throw (§13.10.6) senza espressione può essere usata per generare nuovamente l'eccezione intercettata dal catch blocco. Le assegnazioni a una variabile di eccezione non modificano l'eccezione generata nuovamente.
Esempio: nel codice seguente
class Test { static void F() { try { G(); } catch (Exception e) { Console.WriteLine("Exception in F: " + e.Message); e = new Exception("F"); throw; // re-throw } } static void G() => throw new Exception("G"); static void Main() { try { F(); } catch (Exception e) { Console.WriteLine("Exception in Main: " + e.Message); } } }il metodo
Frileva un'eccezione, scrive alcune informazioni di diagnostica nella console, modifica la variabile di eccezione e genera nuovamente l'eccezione. L'eccezione generata nuovamente è l'eccezione originale, quindi l'output generato è:Exception in F: G Exception in Main: GSe il primo
catchblocco avesse generatoeinvece di rigenerare l'eccezione corrente, l'output prodotto sarebbe stato il seguente:Exception in F: G Exception in Main: Fesempio finale
È un errore di compilazione per un'istruzione break, continue o goto trasferire il controllo fuori da un blocco finally. Quando si verifica un'istruzione , o in un break blocco, la destinazione dell'istruzione deve trovarsi nello stesso continue blocco o in caso contrario si verifica un errore in fase di compilazione.gotofinallyfinally
È un errore in fase di compilazione avere una dichiarazione return in un blocco finally.
Quando l'esecuzione raggiunge un'istruzione try , il controllo viene trasferito al try blocco. Se il controllo raggiunge il punto finale del try blocco senza propagare un'eccezione, il controllo viene trasferito al finally blocco, se presente. Se non esiste alcun finally blocco, il controllo viene trasferito al punto finale dell'istruzione try .
Se è stata propagata un'eccezione, le catch clausole, se presenti, vengono esaminate in ordine lessicale alla ricerca della prima corrispondenza per l'eccezione. La ricerca di una clausola corrispondente catch continua con tutti i blocchi di inclusione, come descritto in §13.10.6. Una catch clausola è una corrispondenza se il tipo di eccezione corrisponde a qualsiasi exception_specifier e qualsiasi exception_filter è true. Una catch clausola senza un exception_specifier corrisponde a qualsiasi tipo di eccezione. Il tipo di eccezione corrisponde al exception_specifier quando il exception_specifier specifica il tipo di eccezione o un tipo di base del tipo di eccezione. Se la clausola contiene un filtro eccezioni, l'oggetto eccezione viene assegnato alla variabile di eccezione e viene valutato il filtro eccezioni. Se la valutazione del boolean_expression per un exception_filter genera un'eccezione, tale eccezione viene intercettata e il filtro eccezioni restituisce false.
Se è stata propagata un'eccezione e viene trovata una clausola corrispondente catch , il controllo viene trasferito al primo blocco corrispondente catch . Se il controllo raggiunge il punto finale del catch blocco senza propagare un'eccezione, il controllo viene trasferito al finally blocco, se presente. Se non esiste alcun finally blocco, il controllo viene trasferito al punto finale dell'istruzione try . Se è stata propagata un'eccezione catch dal blocco, il controllo viene trasferita al finally blocco, se presente. L'eccezione viene propagata all'istruzione contenitore try successiva.
Se è stata propagata un'eccezione e non viene trovata alcuna clausola corrispondente catch, il controllo viene trasferito al blocco finally, se esiste. L'eccezione viene propagata all'istruzione contenitore try successiva.
Le istruzioni di un finally blocco vengono sempre eseguite quando il controllo lascia un'istruzione try . Ciò vale se il trasferimento del controllo si verifica come risultato della normale esecuzione, in seguito all'esecuzione di un'istruzione break, continue, gotoo return come risultato della propagazione di un'eccezione all'esterno dell'istruzione try . Se il controllo raggiunge il punto finale del finally blocco senza propagare un'eccezione, il controllo viene trasferito al punto finale dell'istruzione try .
Se viene generata un'eccezione durante l'esecuzione di un finally blocco e non viene intercettata all'interno dello stesso finally blocco, l'eccezione viene propagata all'istruzione di inclusione try successiva. Se è in corso la propagazione di un'altra eccezione, l'eccezione viene persa. Il processo di propagazione di un'eccezione è illustrato più avanti nella descrizione dell'istruzione throw (§13.10.6).
Esempio: nel codice seguente
public class Test { static void Main() { try { Method(); } catch (Exception ex) when (ExceptionFilter(ex)) { Console.WriteLine("Catch"); } bool ExceptionFilter(Exception ex) { Console.WriteLine("Filter"); return true; } } static void Method() { try { throw new ArgumentException(); } finally { Console.WriteLine("Finally"); } } }il metodo
Methodgenera un'eccezione. La prima azione consiste nell'esaminare le clausole di inclusionecatch, eseguendo eventuali filtri di eccezione. Quindi, la clausola infinallyviene eseguita prima che il controllo venga trasferito alla clausola di corrispondenzaMethodche la racchiude. L'output risultante è:Filter Finally Catchesempio finale
Il try blocco di un'istruzione try è raggiungibile se l'istruzione try è raggiungibile.
Un catch blocco di un'istruzione try è raggiungibile se l'istruzione try è raggiungibile.
Il finally blocco di un'istruzione try è raggiungibile se l'istruzione try è raggiungibile.
Il punto finale di un'istruzione try è raggiungibile se sono soddisfatte entrambe le condizioni seguenti:
- Il punto finale del
tryblocco è raggiungibile o il punto finale di almeno uncatchblocco è raggiungibile. - Se è presente un
finallyblocco, il punto finale delfinallyblocco è raggiungibile.
13.12 Istruzioni controllate e non controllate
checked e unchecked sono istruzioni usate per gestire il contesto del controllo dell'overflow per le operazioni aritmetiche e le conversioni di tipo integrale.
checked_statement
: 'checked' block
;
unchecked_statement
: 'unchecked' block
;
L'istruzione checked fa in modo che tutte le espressioni nel blocco vengano valutate in un contesto controllato e l'istruzione unchecked fa in modo che tutte le espressioni nel blocco vengano valutate in un contesto non selezionato.
Le istruzioni checked e unchecked sono esattamente equivalenti agli operatori checked e unchecked (§12.8.20), ad eccezione del fatto che operano su blocchi anziché su espressioni.
13.13 Istruzione lock
L'istruzione lock ottiene il blocco di esclusione reciproca per un determinato oggetto, esegue un'istruzione e quindi rilascia il blocco.
lock_statement
: 'lock' '(' expression ')' embedded_statement
;
L'espressione di un'istruzione lock indica un valore di un tipo noto come riferimento. Non viene mai eseguita alcuna conversione boxing implicita (§10.2.9) per l'espressione di un'istruzione lock e pertanto si tratta di un errore in fase di compilazione per indicare un valore di un value_type.
Un'istruzione lock del form
lock (x) ...
dove x è un'espressione di un reference_type, equivale esattamente a:
bool __lockWasTaken = false;
try
{
System.Threading.Monitor.Enter(x, ref __lockWasTaken);
...
}
finally
{
if (__lockWasTaken)
{
System.Threading.Monitor.Exit(x);
}
}
con la differenza che x viene valutato una sola volta.
Anche se viene mantenuto un blocco di esclusione reciproca, il codice in esecuzione nello stesso thread di esecuzione può anche ottenere e rilasciare il blocco. Tuttavia, il codice in esecuzione in altri thread non viene bloccato per ottenere il blocco fino al rilascio del blocco.
13.14 Istruzione using
13.14.1 Generale
L'istruzione using ottiene una o più risorse, esegue un'istruzione e quindi elimina la risorsa.
using_statement
: 'await'? 'using' '(' resource_acquisition ')' embedded_statement
;
resource_acquisition
: non_ref_local_variable_declaration
| expression
;
non_ref_local_variable_declaration
: implicitly_typed_local_variable_declaration
| explicitly_typed_local_variable_declaration
;
Un tipo di risorsa è una classe o uno struct non ref che implementa una o entrambe le System.IDisposable interfacce o System.IAsyncDisposable , che include un singolo metodo senza parametri denominato Dispose e/o DisposeAsyncoppure uno struct di riferimento che include un metodo denominato Dispose con la stessa firma di quella dichiarata da System.IDisposable. Il codice che usa una risorsa può chiamare Dispose o DisposeAsync per indicare che la risorsa non è più necessaria.
Se il formato di resource_acquisition è local_variable_declaration , il tipo del local_variable_declaration deve essere dynamic o un tipo di risorsa. Se il formato di resource_acquisition è expression , questa espressione deve avere un tipo di risorsa. Se await è presente, il tipo di risorsa implementerà System.IAsyncDisposable. Un ref struct tipo non può essere il tipo di risorsa per un'istruzione using con il await modificatore.
Le variabili locali dichiarate in un resource_acquisition sono di sola lettura e includono un inizializzatore. Un errore in fase di compilazione si verifica se l'istruzione incorporata tenta di modificare queste variabili locali (tramite l'assegnazione o gli operatori ++ e --), di acquisire l'indirizzo di queste variabili, o di passarle come parametri di riferimento o di output.
Un'istruzione using viene tradotta in tre parti: acquisizione, utilizzo e eliminazione. L'utilizzo della risorsa viene racchiuso in modo implicito in un'istruzione try che include una finally clausola . Questa finally clausola elimina la risorsa. Se l'espressione di acquisizione restituisce null, non viene eseguita alcuna chiamata a Dispose (o DisposeAsync) e non viene generata alcuna eccezione. Se la risorsa è di tipo dynamic , la risorsa viene convertita dinamicamente tramite una conversione dinamica implicita (§10.2.10) in IDisposable (o IAsyncDisposable) durante l'acquisizione per assicurarsi che la conversione venga eseguita correttamente prima dell'utilizzo e dell'eliminazione.
Un'istruzione using del form
using (ResourceType resource = «expression» ) «statement»
corrisponde a una delle tre possibili formulazioni. Per le risorse di classe e struct non ref, quando ResourceType è un tipo di valore non nullable o un parametro di tipo con il vincolo di tipo valore (§15.2.5), la formulazione è semanticamente equivalente a
{
ResourceType resource = «expression»;
try
{
«statement»;
}
finally
{
((IDisposable)resource).Dispose();
}
}
ad eccezione del fatto che il cast di resource a System.IDisposable non causerà la conversione boxing.
In caso contrario, quando ResourceType è dynamic, la formulazione è
{
ResourceType resource = «expression»;
IDisposable d = resource;
try
{
«statement»;
}
finally
{
if (d != null)
{
d.Dispose();
}
}
}
In caso contrario, la formulazione è
{
ResourceType resource = «expression»;
try
{
«statement»;
}
finally
{
IDisposable d = (IDisposable)resource;
if (d != null)
{
d.Dispose();
}
}
}
Per le risorse di ref struct, l'unica formulazione semanticamente equivalente è
{
«ResourceType» resource = «expression»;
try
{
«statement»;
}
finally
{
resource.Dispose();
}
}
In qualsiasi formulazione, la resource variabile è di sola lettura nell'istruzione incorporata e la d variabile è inaccessibile e invisibile a, l'istruzione incorporata.
Una dichiarazione using della forma:
using («expression») «statement»
ha le stesse formulazioni possibili.
Quando un resource_acquisition assume la forma di un local_variable_declaration, è possibile acquisire più risorse di un determinato tipo. Un'istruzione using del form
using (ResourceType r1 = e1, r2 = e2, ..., rN = eN) «statement»
è esattamente equivalente a una sequenza di istruzioni annidate using :
using (ResourceType r1 = e1)
using (ResourceType r2 = e2)
...
using (ResourceType rN = eN)
«statement»
Esempio: l'esempio seguente crea un file denominato log.txt e scrive due righe di testo nel file. L'esempio apre quindi lo stesso file per la lettura e copia le righe di testo contenute nella console.
class Test { static void Main() { using (TextWriter w = File.CreateText("log.txt")) { w.WriteLine("This is line one"); w.WriteLine("This is line two"); } using (TextReader r = File.OpenText("log.txt")) { string s; while ((s = r.ReadLine()) != null) { Console.WriteLine(s); } } } }Poiché le
TextWriterclassi eTextReaderimplementano l'interfacciaIDisposable, l'esempio può usareusingistruzioni per assicurarsi che il file sottostante venga chiuso correttamente dopo le operazioni di scrittura o lettura.esempio finale
Quando ResourceType è un tipo riferimento che implementa IAsyncDisposable. Altre formulazioni per await using eseguire sostituzioni simili dal metodo sincrono Dispose al metodo asincrono DisposeAsync . Un'istruzione await using della forma
await using (ResourceType resource = «expression» ) «statement»
è semanticamente equivalente alle formulazioni illustrate di seguito con IAsyncDisposable invece di IDisposable, DisposeAsync invece di Disposee il Task restituito da DisposeAsync è awaited:
await using (ResourceType resource = «expression» ) «statement»
è semanticamente equivalente a
{
ResourceType resource = «expression»;
try
{
«statement»;
}
finally
{
IAsyncDisposable d = (IAsyncDisposable)resource;
if (d != null)
{
await d.DisposeAsync();
}
}
}
Nota: qualsiasi istruzione jump (§13.10) nella embedded_statement deve essere conforme alla forma espansa dell'istruzione
using. nota finale
13.14.2 Uso della dichiarazione
Una variante sintattica dell'istruzione using è una dichiarazione using.
using_declaration
: 'await'? 'using' non_ref_local_variable_declaration ';' statement_list?
;
Una dichiarazione using ha la stessa semantica di e può essere riscritta come la forma di acquisizione delle risorse corrispondente dell'istruzione using (§13.14.1), come indicato di seguito:
using «local_variable_type» «local_variable_declarators»
// statements
è semanticamente equivalente a
using («local_variable_type» «local_variable_declarators»)
{
// statements
}
e
await using «local_variable_type» «local_variable_declarators»
// statements
è semanticamente equivalente a
await using («local_variable_type» «local_variable_declarators»)
{
// statements
}
La durata delle variabili dichiarate in un non_ref_local_variable_declaration si estende alla fine dell'ambito in cui vengono dichiarate. Tali variabili vengono quindi eliminate nell'ordine inverso in cui vengono dichiarate.
static void M()
{
using FileStream f1 = new FileStream(...);
using FileStream f2 = new FileStream(...), f3 = new FileStream(...);
...
// Dispose f3
// Dispose f2
// Dispose f1
}
Una dichiarazione using non verrà visualizzata direttamente all'interno di un switch_label, ma potrebbe essere all'interno di un blocco all'interno di un switch_label.
13.15 Dichiarazione yield
L'istruzione yield viene utilizzata in un blocco iteratore (§13.3) per restituire un valore all'oggetto enumeratore (§15.15.5) o un oggetto enumerabile (§15.15.6) di un iteratore o per segnalare la fine dell'iterazione.
yield_statement
: 'yield' 'return' expression ';'
| 'yield' 'break' ';'
;
yield è una parola chiave contestuale (§6.4.4) e ha un significato speciale solo quando viene usata immediatamente prima di una return parola chiave o break .
Esistono diverse restrizioni sulla posizione in cui può essere visualizzata un'istruzione yield , come descritto di seguito.
- Si tratta di un errore in fase di compilazione per un'istruzione
yield(di una delle due forme) da visualizzare all'esterno di un method_body, di operator_body o di un accessor_body. - Si tratta di un errore in fase di compilazione per un'istruzione
yield(di una delle due forme) da visualizzare all'interno di una funzione anonima. - È un errore di compilazione che un'istruzione
yield(di una qualsiasi delle due forme) appaia nella clausola di un'istruzionefinallytry. - Si tratta di un errore in fase di compilazione per la visualizzazione di un'istruzione
yield returnin qualsiasi punto di un'istruzionetrycontenente qualsiasi catch_clauses.
Esempio: l'esempio seguente mostra alcuni usi validi e non validi delle
yieldistruzioni.delegate IEnumerable<int> D(); IEnumerator<int> GetEnumerator() { try { yield return 1; // Ok yield break; // Ok } finally { yield return 2; // Error, yield in finally yield break; // Error, yield in finally } try { yield return 3; // Error, yield return in try/catch yield break; // Ok } catch { yield return 4; // Error, yield return in try/catch yield break; // Ok } D d = delegate { yield return 5; // Error, yield in an anonymous function }; } int MyMethod() { yield return 1; // Error, wrong return type for an iterator block }esempio finale
Una conversione implicita (§10.2) deve esistere dal tipo dell'espressione nell'istruzione yield return al tipo di rendimento (§15.15.4) dell'iteratore.
Un'istruzione yield return viene eseguita come segue:
- L'espressione specificata nell'istruzione viene valutata, convertita in modo implicito nel tipo yield e assegnata alla
Currentproprietà dell'oggetto enumeratore. - L'esecuzione del blocco iteratore viene sospesa. Se l'istruzione
yield returnsi trova all'interno di uno o piùtryblocchi, i blocchi associatifinallynon vengono eseguiti in questo momento. - Il
MoveNextmetodo dell'oggetto enumeratore restituiscetrueal chiamante, a indicare che l'oggetto enumeratore è stato avanzato correttamente all'elemento successivo.
La chiamata successiva al metodo dell'enumeratore MoveNext riprende l'esecuzione del blocco iteratore dal punto in cui è stato sospeso.
Un'istruzione yield break viene eseguita come segue:
- Se l'istruzione
yield breakè racchiusa da uno o piùtryblocchi con blocchi associatifinally, il controllo viene inizialmente trasferito alfinallyblocco dell'istruzione piùtryinterna. Quando e se il controllo raggiunge il punto finale di unfinallyblocco, il controllo viene trasferito alfinallyblocco dell'istruzione di inclusionetrysuccessiva. Questo processo viene ripetuto fino a quando non vengono eseguiti ifinallyblocchi di tutte le istruzioni che racchiudonotry. - Il controllo viene restituito al chiamante del blocco iteratore. Questo è il metodo
MoveNexto il metodoDisposedell'oggetto enumeratore.
Poiché un'istruzione yield break trasferisce in modo incondizionato il controllo altrove, il punto finale di un'istruzione yield break non è mai raggiungibile.
ECMA C# draft specification