13 Istruzioni
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 (§23.2) e fixed_statement (§23.7) sono disponibili solo nel codice non sicuro (§23).
Il embedded_statement non deterministico viene usato per le istruzioni visualizzate all'interno di altre istruzioni. L'uso di embedded_statement anziché l'istruzione esclude l'uso 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
if
richiede un embedded_statement anziché un'istruzione per il relativoif
ramo. Se questo codice fosse consentito, la variabilei
verrebbe dichiarata, ma non potrà mai essere usata. Si noti, tuttavia, che inserendoi
la dichiarazione in un blocco, l'esempio è valido.esempio finale
13.2 End points and reachability
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, il 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.23) 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
if
costante perché entrambi gli operandi dell'operatore==
sono costanti. Poiché l'espressione costante viene valutata in fase di compilazione, producendo il valorefalse
, laConsole.WriteLine
chiamata viene considerata non raggiungibile. Tuttavia, sei
viene modificato come variabile localevoid F() { int i = 1; if (i == 2) Console.WriteLine("reachable"); }
la
Console.WriteLine
chiamata è considerata raggiungibile, anche se, 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.WriteLine
viene determinata nel modo seguente:
- La prima
Console.WriteLine
istruzione di espressione è raggiungibile perché il blocco delF
metodo è raggiungibile (§13.3).- Il punto finale della prima
Console.WriteLine
istruzione dell'espressione è raggiungibile perché tale istruzione è raggiungibile (§13.7 e §13.3).- L'istruzione
if
è raggiungibile perché il punto finale della primaConsole.WriteLine
istruzione dell'espressione è raggiungibile (§13.7 e §13.3).- La seconda
Console.WriteLine
istruzione di espressione è raggiungibile perché l'espressione booleana dell'istruzioneif
non ha il valorefalse
costante .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
switch
non consente a una sezione switch di passare alla sezione switch successiva, si tratta di un errore in fase di compilazione per il punto finale dell'elenco di 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 nei contesti in cui ne è consentita 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 viene detto 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.14). Alcune restrizioni aggiuntive si applicano ai blocchi iteratori:
- Si tratta di un errore in fase di compilazione per la visualizzazione di un'istruzione
return
in un blocco iteratore (mayield return
le istruzioni sono consentite). - Si tratta di un errore in fase di compilazione per un blocco iteratore che contiene un contesto non sicuro (§23.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 istruzioni si verificano nel blocco s (§13.3) e in switch_blocks (§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'istruzione in un elenco di istruzioni è raggiungibile se almeno una delle condizioni seguenti è vera:
- L'istruzione è la prima istruzione e l'elenco di istruzioni stesso è raggiungibile.
- 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
while
con 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. Si tratta di un errore in fase di compilazione per due etichette con lo stesso nome per avere ambiti sovrapposti.
È possibile fare riferimento a un'etichetta dalle goto
istruzioni (§13.10.4) nell'ambito dell'etichetta.
Nota: ciò significa che
goto
le istruzioni possono trasferire il controllo all'interno di blocchi e blocchi, ma mai in 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 in blocchi e blocchi switch, ma non sono consentite come istruzioni incorporate.
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
var
e l'input corrisponde implicitly_typed_local_variable_declaration 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 è rispettivamente uno dei implicitly_typed_local_variable_declarator, explicitly_typed_local_variable_declarator o ref_local_variable_declarator per variabili locali tipizzate in modo implicito e tipizzato in modo esplicito. Il dichiaratore definisce il nome (identificatore) e il valore iniziale, se presente, della variabile introdotta.
Se in una dichiarazione sono presenti più dichiaratori, vengono elaborati, incluse le espressioni di inizializzazione, per sinistra verso 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 del relativo 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 ref
variable_reference; il relativo tipo è ref T?
quando T
è un tipo riferimento non nullable; in caso contrario, il relativo 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 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 itself
esempio 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 explicity_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.21.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.21.3).
Se ref_kind è ref readonly
, gli identificatori dichiarati sono riferimenti a variabili considerate di sola lettura. In caso contrario, se ref_kind è ref
, gli identificatori dichiarati sono riferimenti a variabili che devono essere scrivibili.
Si tratta di un errore in fase di compilazione per dichiarare una variabile locale ref o una variabile di un tipo, all'interno di un ref struct
metodo dichiarato con il method_modifier async
o all'interno di un iteratore (§15.14).
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.23) 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, tutte le eccezioni vengono osservate solo quando l'attività restituita è attesa. 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 gli local_function_declarationdi overload non sono consentiti.
Un local_function_declaration può includere un async
modificatore (§15.15) e un unsafe
modificatore (§23.1). Se la dichiarazione include il async
modificatore, il tipo restituito sarà void
o un «TaskType»
tipo (§15.15.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 per type_parameter_list o parameter_list contenere attributi. Se la funzione locale viene dichiarata in un contesto non sicuro (§23.2), la funzione locale può includere codice unsafe, anche se la dichiarazione della funzione locale non include il unsafe
modificatore.
Una funzione locale viene dichiarata nell'ambito del 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. Il compilatore determina quali variabili sono sicuramente assegnate in caso di restituzione (§9.4.4.33).
Quando il tipo di è un tipo di struct, si tratta di this
un errore in fase di compilazione per il corpo di una funzione locale per accedere this
a . 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 tratta di un errore in fase di compilazione per il corpo della funzione locale per contenere un'istruzione, un'istruzione goto
break
o un'istruzione continue
la cui destinazione è esterna al corpo della funzione locale.
Nota: le regole precedenti per
this
egoto
rispecchiano le regole per le funzioni anonime in §12.19.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 diL
non è raggiungibile. Poiché il punto iniziale diL
non è raggiungibile, l'istruzione che segue l'endpoint diL
non è 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
this
nébase
membri dell'istanza da un riferimento implicitothis
, né variabili locali, parametri o funzioni locali non statiche dall'ambito di inclusione. 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 istruzioni.
Nota: in particolare, le espressioni come
x + y
ex == 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
if
del 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.24) 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
false
e se è presente unaelse
parte, 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
false
e se unaelse
parte 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 false
costante .
La seconda istruzione incorporata di un'istruzione if
, se presente, è raggiungibile se l'istruzione if
è raggiungibile e l'espressione booleana non ha il valore true
costante .
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 true
costante .
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 dell'espressione switch.
switch_statement
: 'switch' '(' expression ')' switch_block
;
switch_block
: '{' switch_section* '}'
;
switch_section
: switch_label+ statement_list
;
switch_label
: 'case' pattern case_guard? ':'
| 'default' ':'
;
case_guard
: 'when' expression
;
Un switch_statement è costituito dalla parola chiave switch
, seguita da un'espressione tra parentesi (denominata espressione switch), seguita da un switch_block. Il switch_block è costituito da zero o più switch_sections, racchiuso 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 criterio associato (§11) rispetto al quale viene testato il valore dell'espressione switch. 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.
Il tipo di controllo di un'istruzione switch
viene stabilito dall'espressione switch.
- Se il tipo dell'espressione switch è
sbyte
,byte
,short
ushort
, ,int
uint
,long
,bool
string
char
ulong
o 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 dell'espressione switch a uno dei tipi di governance seguenti:
sbyte
,byte
short
, ,ushort
ulong
int
long
char
uint
,string
o un tipo 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 dell'espressione switch. 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 switch è sottosumed da (§11.3) il set di modelli di etichette switch precedenti dell'istruzione switch che non hanno un case guard o il cui case guard è un'espressione costante con il 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:
- L'espressione switch viene valutata e convertita nel tipo di controllo.
- Il controllo viene trasferito in base al valore dell'espressione switch convertita:
- Il primo criterio lessicalmente nel set di
case
etichette nella stessaswitch
istruzione che corrisponde al valore dell'espressione switch e per il quale l'espressione guard è 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, il compilatore è necessario per determinare il primo criterio lessicalmente che corrisponde all'espressione e per il quale la clausola guard è assente o restituisce
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 regola è nota come regola "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, verrà usata un'istruzione o
goto default
esplicitagoto case
: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
break
le istruzioni vengono accidentalmente omesse. Ad esempio, le sezioni dell'istruzioneswitch
precedente 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 case
ogoto default
, ma qualsiasi costrutto che esegue il rendering del punto finale dell'elenco di istruzioni non raggiungibile è consentito. Ad esempio, un'istruzionewhile
controllata dall'espressionetrue
booleana è nota per non raggiungere mai il punto finale. Analogamente, un'istruzionethrow
oreturn
trasferisce 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
switch
può 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 di stringa (§12.12.8), l'istruzione
switch
fa distinzione tra maiuscole e minuscole e eseguirà una sezione switch specifica solo se la stringa dell'espressione switch corrisponde esattamente a unacase
costante di etichetta. nota finale Quando il tipo di regola di un'istruzioneswitch
èstring
o un tipo valore nullable, il valorenull
è consentito come costante dicase
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:
- L'espressione switch è un valore costante e
- l'etichetta è un
case
il cui criterio corrisponde (§11.2.1) tale valore e la protezione dell'etichetta è assente o non è un'espressione costante con il valore false; o - è un'etichetta
default
e nessuna sezione switch contiene un'etichetta case il cui criterio corrisponde a tale valore e la cui protezione è assente o un'espressione costante con il valore true.
- l'etichetta è un
- L'espressione switch non è un valore costante e
- l'etichetta è senza
case
una protezione o con una protezione il cui valore non è la costante false; o - è un'etichetta
default
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 cambio; o
- Il tipo di controllo dell'opzione è un tipo nullable 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 contiene un criterio che corrisponde al valore
null
.
- l'etichetta è senza
- L'etichetta switch fa riferimento a un'istruzione o
goto default
raggiungibilegoto case
.
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 è true:
- L'istruzione
switch
contiene un'istruzione raggiungibilebreak
che esce dall'istruzioneswitch
. - Nessuna
default
etichetta è presente e- L'espressione switch è un valore non costante e il set di criteri 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 switch.
- L'espressione switch è 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
. - L'espressione switch è un valore costante e nessuna
case
etichetta senza una protezione o la cui protezione è la costante true corrisponde a tale valore.
Esempio: il codice seguente mostra un uso conciso della
when
clausola :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.24) 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 continue
(§13.10.3) può essere usata per trasferire il controllo al punto finale dell'istruzione while
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 false
costante .
Il punto finale di un'istruzione while
è raggiungibile se almeno una delle condizioni seguenti è vera:
- L'istruzione
while
contiene un'istruzione raggiungibilebreak
che esce dall'istruzionewhile
. - L'istruzione
while
è raggiungibile e l'espressione booleana non ha il valoretrue
costante .
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.24). 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 continue
(§13.10.3) può essere usata per trasferire il controllo al punto finale dell'istruzione do
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
do
contiene un'istruzione raggiungibilebreak
che esce dall'istruzionedo
. - Il punto finale dell'istruzione incorporata è raggiungibile e l'espressione booleana non ha il valore
true
costante .
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.24).
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 un'istruzione (§13.10.3) può essere usata per trasferire il controllo al punto finale dell'istruzione for
incorporata (eseguendo così il for_iterator ed eseguendo un'altra continue
iterazione dell'istruzionefor
, a partire 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 valorefalse
costante .
Il punto finale di un'istruzione for
è raggiungibile se almeno una delle condizioni seguenti è vera:
- L'istruzione
for
contiene un'istruzione raggiungibilebreak
che esce dall'istruzionefor
. - L'istruzione
for
è raggiungibile e un for_condition è presente e non ha il valoretrue
costante .
13.9.5 Istruzione foreach
L'istruzione foreach
enumera gli elementi di una raccolta, eseguendo un'istruzione incorporata per ogni elemento della raccolta.
foreach_statement
: '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.
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.
Nell'esempio seguente, per brevità, IEnumerable
, IEnumerable<T>
IEnumerator
e IEnumerator<T>
fare riferimento ai tipi corrispondenti negli spazi dei nomi System.Collections
e System.Collections.Generic
.
L'elaborazione in fase di compilazione di un'istruzione foreach
determina innanzitutto il tipo di raccolta, il tipo di enumeratore e il tipo di iterazione dell'espressione. Questa determinazione procede come segue:
- Se il tipo di espressione è un tipo
X
di matrice, è presente una conversione implicita dei riferimenti da X all'interfacciaIEnumerable
,poichéSystem.Array
implementa questa interfaccia. Il tipo di raccolta è l'interfacciaIEnumerable
, il tipo di enumeratore è l'interfacciaIEnumerator
e il tipo di iterazione è il tipo di elemento del tipoX
di matrice . - Se il tipo
X
di espressione èdynamic
presente una conversione implicita dall'espressione all'interfacciaIEnumerable
(§10.2.10). Il tipo di raccolta è l'interfacciaIEnumerable
e il tipo di enumeratore è l'interfacciaIEnumerator
. Se l'identificatorevar
viene assegnato come local_variable_type , il tipo di iterazione èdynamic
, in caso contrario èobject
. - In caso contrario, determinare se il tipo
X
ha un metodo appropriatoGetEnumerator
:- Eseguire la ricerca dei membri nel tipo
X
con identificatoreGetEnumerator
e senza argomenti 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 di seguito. È consigliabile generare un avviso se la ricerca dei membri produce elementi tranne 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. È consigliabile generare un avviso se la risoluzione dell'overload produce qualsiasi elemento tranne un metodo di istanza pubblica non ambiguo o nessun metodo applicabile.
- Se il tipo
E
restituito delGetEnumerator
metodo non è una classe, uno struct o un tipo di interfaccia, viene generato un errore e non vengono eseguiti altri passaggi. - La ricerca dei membri viene eseguita
E
con l'identificatoreCurrent
e senza argomenti di tipo. Se la ricerca del membro non produce corrispondenze, il risultato è un errore o il risultato è qualsiasi elemento tranne una proprietà dell'istanza pubblica che consente la lettura, viene generato un errore e non vengono eseguiti altri passaggi. - La ricerca dei membri viene eseguita
E
con l'identificatoreMoveNext
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, viene generato un errore e non vengono eseguiti altri passaggi. - La risoluzione dell'overload viene eseguita nel gruppo di metodi con 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 oppure il tipo restituito non
bool
è , viene generato un errore e non vengono eseguiti altri passaggi. - Il tipo di raccolta è
X
, il tipo di enumeratore èE
e il tipo di iterazione è il tipo dellaCurrent
proprietà . LaCurrent
proprietà può includere ilref
modificatore, nel qual caso l'espressione restituita è un variable_reference (§9.5) che è facoltativamente di sola lettura.
- Eseguire la ricerca dei membri nel tipo
- In caso contrario, verificare la presenza di un'interfaccia enumerabile:
- Se tra tutti i tipi
Tᵢ
per cui è presente una conversione implicita daX
aIEnumerable<Tᵢ>
, esiste un tipoT
univoco cheT
nondynamic
è e per tutti gli altriTᵢ
è presente una conversione implicita daIEnumerable<T>
aIEnumerable<Tᵢ>
, il tipo di raccolta è l'interfacciaIEnumerable<T>
, il tipo di enumeratore è l'interfacciaIEnumerator<T>
e il tipo di iterazione èT
. - In caso contrario, se è presente più di un tipo di questo tipo
T
, viene generato un errore e non vengono eseguiti altri passaggi. - In caso contrario, se è presente una conversione implicita da
X
all'interfacciaSystem.Collections.IEnumerable
, il tipo di raccolta è questa interfaccia, il tipo di enumeratore è l'interfacciaSystem.Collections.IEnumerator
e il tipo di iterazione èobject
. - In caso contrario, viene generato un errore e non vengono eseguiti altri passaggi.
- Se tra tutti i tipi
I passaggi precedenti, se hanno esito positivo, producono in modo non ambiguo un tipo di raccolta , un tipo C
di enumeratore e un tipo E
di T
iterazione , ref T
o ref readonly T
. 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.21.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.
Nota: se
x
ha il valorenull
, viene generata un'eccezioneSystem.NullReferenceException
in 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 l'espansione precedente.
La posizione all'interno v
del while
ciclo è importante per la modalità di acquisizione (§12.19.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
v
nel formato espanso fosse dichiarato all'esterno delwhile
ciclo, verrebbe condiviso tra tutte le iterazioni e il relativo valore dopo ilfor
ciclo sarebbe il valore finale, ,13
che è ciò che la chiamata dif
verrebbe stampata. Poiché ogni iterazione ha invece una propria variabilev
, quella acquisita daf
nella prima iterazione continuerà a contenere il valore7
, ovvero ciò che verrà stampato. Si noti che le versioni precedenti di C# dichiaratev
all'esterno delwhile
ciclo.esempio finale
Il corpo del finally
blocco viene costruito in base ai passaggi seguenti:
Se è presente una conversione implicita da
E
all'interfacciaSystem.IDisposable
,Se
E
è un tipo di valore non nullable, lafinally
clausola viene espansa fino all'equivalente semantico di:finally { ((System.IDisposable)e).Dispose(); }
In caso contrario, la
finally
clausola 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 valore o un parametro di tipo di cui è stata creata un'istanza in un tipo valore, la conversione di inSystem.IDisposable
non causerà l'esecuzione dele
boxing.
In caso contrario, se
E
è un tipo sealed, lafinally
clausola viene espansa in un blocco vuoto:finally {}
In caso contrario, la
finally
clausola 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.9
esempio finale
Esempio: nell'esempio seguente
int[] numbers = { 1, 3, 5, 7, 9 }; foreach (var n in numbers) { Console.WriteLine(n); }
il tipo di
n
viene dedotto comeint
, il tipo di iterazione dinumbers
.esempio finale
13.10 Istruzioni Jump
13.10.1 Generale
Le istruzioni jump trasferisce 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ù try
blocchi con blocchi associati finally
, il controllo viene inizialmente trasferito al finally
blocco dell'istruzione più try
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
try
istruzioni vengono eseguiti prima chefinally
il controllo venga trasferito alla destinazione dell'istruzione jump. L'output prodotto è il seguente:Before break Innermost finally block Outermost finally block After break
esempio finale
13.10.2 Istruzione break
L'istruzione break
esce dall'istruzione , , while
do
for
, o foreach
più switch
vicina.
break_statement
: 'break' ';'
;
La destinazione di un'istruzione break
è il punto finale dell'istruzione , for
while
do
, , o foreach
più switch
vicina. Se un'istruzione break
non è racchiusa da un'istruzione switch
, while
, do
for
, o foreach
, si verifica un errore in fase di compilazione.
Quando più switch
istruzioni , do
while
, for
, o foreach
sono annidate tra loro, 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ùtry
blocchi con blocchi associatifinally
, il controllo viene inizialmente trasferito alfinally
blocco dell'istruzione piùtry
interna. Quando e se il controllo raggiunge il punto finale di unfinally
blocco, il controllo viene trasferito alfinally
blocco dell'istruzione di inclusionetry
successiva. Questo processo viene ripetuto fino a quando non vengono eseguiti ifinally
blocchi 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 , do
for
, , o foreach
più while
vicina.
continue_statement
: 'continue' ';'
;
La destinazione di un'istruzione continue
è il punto finale dell'istruzione incorporata dell'istruzione contenitore più while
vicina, do
, for
, o foreach
. Se un'istruzione continue
non è racchiusa da un'istruzione while
, do
, for
o foreach
, si verifica un errore in fase di compilazione.
Quando più while
istruzioni , do
, for
o 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ùtry
blocchi con blocchi associatifinally
, il controllo viene inizialmente trasferito alfinally
blocco dell'istruzione piùtry
interna. Quando e se il controllo raggiunge il punto finale di unfinally
blocco, il controllo viene trasferito alfinally
blocco dell'istruzione di inclusionetry
successiva. Questo processo viene ripetuto fino a quando non vengono eseguiti ifinally
blocchi 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 trasferisce il goto
controllo a un'istruzione contrassegnata da un'etichetta.
goto_statement
: 'goto' identifier ';'
| 'goto' 'case' constant_expression ';'
| 'goto' 'default' ';'
;
La destinazione di un'istruzione goto
identifier è 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
goto
per 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
goto
viene 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ùtry
blocchi con blocchi associatifinally
, il controllo viene inizialmente trasferito alfinally
blocco dell'istruzione piùtry
interna. Quando e se il controllo raggiunge il punto finale di unfinally
blocco, il controllo viene trasferito alfinally
blocco dell'istruzione di inclusionetry
successiva. Questo processo viene ripetuto fino a quando non vengono eseguiti ifinally
blocchi 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; un'espressione che contiene ref
è denominata return-by-ref e una che contiene solo un'espressione è denominata return-by-value.
Si tratta di un errore in fase di compilazione per usare un valore restituito-no-value da un metodo dichiarato come return-by-value o returns-by-ref (§15.6.1).
Si tratta di un errore in fase di compilazione per usare un return-by-ref da un metodo dichiarato come returns-no-value o returns-by-value.
Si tratta di un errore in fase di compilazione per usare un valore restituito per valore da un metodo dichiarato come return-no-value o returns-by-ref.
Si tratta di un errore in fase di compilazione per usare un'espressione return-by-ref se expression non è un variable_reference o è un riferimento a una variabile il cui contesto ref-safe-context non è caller-context (§9.7.2).
Si tratta di un errore in fase di compilazione per usare un oggetto return-by-ref da un metodo dichiarato con il method_modifier async
.
Un membro della funzione viene detto di calcolare un valore se si tratta di un metodo con un metodo returns-by-value (§15.6.11), una funzione di accesso get returns-by-value di una proprietà o di un indicizzatore o di un operatore definito dall'utente. I membri della funzione che sono return-no-value non calcolano un valore e sono metodi con il tipo void
restituito effettivo , le funzioni di accesso set di proprietà e indicizzatori, aggiungere e rimuovere funzioni di accesso di eventi, costruttori di istanza, costruttori statici e finalizzatori. I membri della funzione che sono returns-by-ref non calcolano un valore.
Per un valore restituito, esiste una conversione implicita (§10.2) dal tipo di espressione al tipo restituito effettivo (§15.6.11) del membro della funzione contenitore. Per un valore return-by-ref, esiste 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.19) e partecipano alla determinazione delle conversioni esistenti per tali funzioni (§10.7.1).
Si tratta di un errore in fase di compilazione per la visualizzazione di un'istruzione return
in un finally
blocco (§13.11).
Un'istruzione return
viene eseguita come segue:
- Per un valore restituito, viene valutata un'espressione e il relativo valore viene convertito nel tipo restituito effettivo della funzione contenitore tramite una conversione implicita. Il risultato della conversione diventa il valore del risultato prodotto dalla funzione . Per un valore return-by-ref, 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ùcatch
try
blocchi con blocchi associatifinally
, il controllo viene inizialmente trasferito alfinally
blocco dell'istruzione piùtry
interna. Quando e se il controllo raggiunge il punto finale di unfinally
blocco, il controllo viene trasferito alfinally
blocco dell'istruzione di inclusionetry
successiva. Questo processo viene ripetuto fino a quando non vengono eseguiti ifinally
blocchi di tutte le istruzioni di inclusionetry
. - 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.15.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.Exception
e il risultato della valutazione dell'espressione viene convertito in System.Exception
prima di essere generato. Se il risultato della conversione è null
, viene invece generato un System.NullReferenceException
oggetto .
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 (§21.4).
Nel membro della funzione corrente viene esaminata ogni
try
istruzione che racchiude il punto throw. Per ogni istruzioneS
, a partire dall'istruzione piùtry
interna e terminando con l'istruzione più esternatry
, vengono valutati i passaggi seguenti:- Se il blocco di racchiude il
try
punto diS
generazione e seS
ha una o piùcatch
clausole, lecatch
clausole vengono esaminate in ordine di aspetto per individuare un gestore appropriato per l'eccezione. La primacatch
clausola che specifica un tipo di eccezione (o un parametro di tipoT
che in fase di esecuzione indica un tipo di eccezione ) in modo che il tipoT
di runtime diE
deriva daT
venga 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 unacatch
clausola contiene un filtro di eccezione, talecatch
clausola 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 talecatch
clausola. - In caso contrario, se il
try
blocco o uncatch
blocco di racchiude il punto throwS
e seS
ha unfinally
blocco, il controllo viene trasferito alfinally
blocco. Se ilfinally
blocco genera un'altra eccezione, l'elaborazione dell'eccezione corrente viene terminata. In caso contrario, quando il controllo raggiunge il punto finale delfinally
blocco, l'elaborazione dell'eccezione corrente viene continuata.
- Se il blocco di racchiude il
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.15.3.
Se la funzione corrente è asincrona e
void
restituisce , il contesto di sincronizzazione del thread corrente viene informato come descritto in §15.15.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 della exception_filter e catch
del blocco, 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
. Un'istruzione try
può avere una sola clausola generale catch
e, se presente, sarà l'ultima catch
clausola.
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 generalecatch
può 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
F
rileva 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: G
Se il primo
catch
blocco aveva generatoe
invece di rigenerare l'eccezione corrente, l'output prodotto sarà il seguente:Exception in F: G Exception in Main: F
esempio finale
Si tratta di un errore in fase di compilazione per un'istruzione break
, continue
o goto
per trasferire il controllo da un finally
blocco. Quando si verifica un'istruzione , o in un finally
blocco, la destinazione dell'istruzione deve trovarsi nello stesso finally
blocco o in caso contrario si verifica un errore in fase di compilazione.goto
continue
break
Si tratta di un errore in fase di compilazione per l'esecuzione di un'istruzione return
in un finally
blocco.
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 è 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 trasferisce al finally
blocco, se esistente. 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
, goto
o 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
Method
genera un'eccezione. La prima azione consiste nell'esaminare le clausole di inclusionecatch
, eseguendo eventuali filtri di eccezione. Quindi, la clausola inMethod
viene eseguita prima delfinally
trasferimento del controllo alla clausola di corrispondenzacatch
di inclusione. L'output risultante è:Filter Finally Catch
esempio 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
try
blocco è raggiungibile o il punto finale di almeno uncatch
blocco è raggiungibile. - Se è presente un
finally
blocco, il punto finale delfinally
blocco è raggiungibile.
13.12 Istruzioni controllate e non controllate
Le checked
istruzioni e unchecked
vengono usate per controllare il contesto di controllo dell'overflow per operazioni aritmetiche e 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 checked
istruzioni e sono esattamente equivalenti agli checked
operatori e unchecked
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
L'istruzione using
ottiene una o più risorse, esegue un'istruzione e quindi elimina la risorsa.
using_statement
: 'using' '(' resource_acquisition ')' embedded_statement
;
resource_acquisition
: local_variable_declaration
| expression
;
Una risorsa è una classe o uno struct che implementa l'interfaccia System.IDisposable
, che include un singolo metodo senza parametri denominato Dispose
. Il codice che usa una risorsa può chiamare Dispose
per indicare che la risorsa non è più necessaria.
Se la forma di resource_acquisition è local_variable_declaration, il tipo del local_variable_declaration deve essere dynamic
o un tipo che può essere convertito in modo implicito in System.IDisposable
. Se la forma di resource_acquisition è espressione , questa espressione sarà convertibile in modo implicito in System.IDisposable
.
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 --
), accettare l'indirizzo di tali variabili o passarle come parametri di riferimento o 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 viene acquisita una null
risorsa, non viene eseguita alcuna chiamata a Dispose
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
durante l'acquisizione per garantire 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 espansioni. Quando ResourceType
è un tipo di valore non nullable o un parametro di tipo con il vincolo di tipo valore (§15.2.5), l'espansione è 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
, l'espansione è
{
ResourceType resource = «expression»;
IDisposable d = resource;
try
{
«statement»;
}
finally
{
if (d != null)
{
d.Dispose();
}
}
}
In caso contrario, l'espansione è
{
ResourceType resource = «expression»;
try
{
«statement»;
}
finally
{
IDisposable d = (IDisposable)resource;
if (d != null)
{
d.Dispose();
}
}
}
In qualsiasi espansione, la resource
variabile è di sola lettura nell'istruzione incorporata e la d
variabile è inaccessibile e invisibile all'istruzione incorporata.
Un'implementazione può implementare un determinato using_statement in modo diverso, ad esempio per motivi di prestazioni, purché il comportamento sia coerente con l'espansione precedente.
Istruzione using
del formato:
using («expression») «statement»
ha le stesse tre possibili espansioni. In questo caso ResourceType
è implicitamente il tipo in fase di compilazione dell'espressione, se presente. In caso contrario, l'interfaccia IDisposable
stessa viene usata come ResourceType
. La resource
variabile non è accessibile e invisibile all'istruzione incorporata.
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
TextWriter
classi eTextReader
implementano l'interfacciaIDisposable
, l'esempio può usareusing
istruzioni per assicurarsi che il file sottostante venga chiuso correttamente dopo le operazioni di scrittura o lettura.esempio finale
13.15 Dichiarazione yield
L'istruzione yield
viene utilizzata in un blocco iteratore (§13.3) per restituire un valore all'oggetto enumeratore (§15.14.5) o un oggetto enumerabile (§15.14.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. - Si tratta di un errore in fase di compilazione per la visualizzazione di un'istruzione
yield
(di una delle due forme) nella clausola di un'istruzionefinally
try
. - Si tratta di un errore in fase di compilazione per la visualizzazione di un'istruzione
yield return
in qualsiasi punto di un'istruzionetry
contenente qualsiasi catch_clauses.
Esempio: l'esempio seguente mostra alcuni usi validi e non validi delle
yield
istruzioni.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
Esiste una conversione implicita (§10.2) dal tipo dell'espressione nell'istruzione yield return
al tipo di rendimento (§15.14.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
Current
proprietà dell'oggetto enumeratore. - L'esecuzione del blocco iteratore viene sospesa. Se l'istruzione
yield return
si trova all'interno di uno o piùtry
blocchi, i blocchi associatifinally
non vengono eseguiti in questo momento. - Il
MoveNext
metodo dell'oggetto enumeratore restituiscetrue
al 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 da cui è stata sospesa l'ultima volta.
Un'istruzione yield break
viene eseguita come segue:
- Se l'istruzione
yield break
è racchiusa da uno o piùtry
blocchi con blocchi associatifinally
, il controllo viene inizialmente trasferito alfinally
blocco dell'istruzione piùtry
interna. Quando e se il controllo raggiunge il punto finale di unfinally
blocco, il controllo viene trasferito alfinally
blocco dell'istruzione di inclusionetry
successiva. Questo processo viene ripetuto fino a quando non vengono eseguiti ifinally
blocchi di tutte le istruzioni di inclusionetry
. - Il controllo viene restituito al chiamante del blocco iteratore. Si tratta del metodo o
Dispose
delMoveNext
metodo dell'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