Condividi tramite


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 relativo if ramo. Se questo codice fosse consentito, la variabile i verrebbe dichiarata, ma non potrà mai essere usata. Si noti, tuttavia, che inserendo ila 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 valore false, la Console.WriteLine chiamata viene considerata non raggiungibile. Tuttavia, se i viene modificato come variabile locale

void 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 del F 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 prima Console.WriteLine istruzione dell'espressione è raggiungibile (§13.7 e §13.3).
  • La seconda Console.WriteLine istruzione di espressione è raggiungibile perché l'espressione booleana dell'istruzione if non ha il valore falsecostante .

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'istruzione break .

  • 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 (ma yield 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 thisa . 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 e goto 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 di L non è raggiungibile. Poiché il punto iniziale di L non è raggiungibile, l'istruzione che segue l'endpoint di L 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 thisbase membri dell'istanza da un riferimento implicito this , né variabili locali, parametri o funzioni locali non statiche dall'ambito di inclusione. Tuttavia, tutte queste sono consentite in un'espressione nameof() .

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 e x == 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 form

if (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'istruzione if .
  • Se l'espressione booleana restituisce false e se è presente una else 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'istruzione if .
  • Se l'espressione booleana restituisce false e se una else parte non è presente, il controllo viene trasferito al punto finale dell'istruzione if .

La prima istruzione incorporata di un'istruzione if è raggiungibile se l'istruzione if è raggiungibile e l'espressione booleana non ha il valore falsecostante .

La seconda istruzione incorporata di un'istruzione if , se presente, è raggiungibile se l'istruzione if è raggiungibile e l'espressione booleana non ha il valore truecostante .

Il punto finale di un'istruzione if è raggiungibile se il punto finale di almeno una delle relative istruzioni incorporate è raggiungibile. Inoltre, il punto finale di un'istruzione if senza else parti è raggiungibile se l'istruzione if è raggiungibile e l'espressione booleana non ha il valore truecostante .

13.8.3 Istruzione switch

L'istruzione switch seleziona per l'esecuzione di un elenco di istruzioni con un'etichetta switch associata che corrisponde al valore 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, shortushort, , intuint, long, boolstringcharulongo 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, byteshort, , ushortulongintlongcharuint, stringo 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 stessa switch 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 corrispondente case .
    • In caso contrario, se è presente un'etichetta default , il controllo viene trasferito all'elenco di istruzioni che segue l'etichetta default .
    • In caso contrario, il controllo viene trasferito al punto finale dell'istruzione switch .

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: e default: 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'istruzione switch 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 caseo goto default , ma qualsiasi costrutto che esegue il rendering del punto finale dell'elenco di istruzioni non raggiungibile è consentito. Ad esempio, un'istruzione while controllata dall'espressione true booleana è nota per non raggiungere mai il punto finale. Analogamente, un'istruzione throw o return 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 tipo string. 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 una case costante di etichetta. nota finale Quando il tipo di regola di un'istruzione switch è string o un tipo valore nullable, il valore null è consentito come costante di case etichetta.

I statement_listdi un switch_block possono contenere istruzioni di dichiarazione (§13.6). L'ambito di una variabile locale o di una costante dichiarata in un blocco switch è il blocco switch.

Un'etichetta switch è raggiungibile se almeno una delle condizioni seguenti è vera:

  • 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'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 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 raggiungibile break che esce dall'istruzione switch .
  • 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'istruzione continue ), il controllo viene trasferito all'inizio dell'istruzione while .
  • Se l'espressione booleana restituisce false, il controllo viene trasferito al punto finale dell'istruzione while .

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 falsecostante .

Il punto finale di un'istruzione while è raggiungibile se almeno una delle condizioni seguenti è vera:

  • L'istruzione while contiene un'istruzione raggiungibile break che esce dall'istruzione while .
  • L'istruzione while è raggiungibile e l'espressione booleana non ha il valore truecostante .

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 restituisce true, il controllo viene trasferito all'inizio dell'istruzione do . In caso contrario, il controllo viene trasferito al punto finale dell'istruzione do .

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 raggiungibile break che esce dall'istruzione do .
  • Il punto finale dell'istruzione incorporata è raggiungibile e l'espressione booleana non ha il valore truecostante .

13.9.4 Istruzione for

L'istruzione for valuta una sequenza di espressioni di inizializzazione e quindi, mentre una condizione è true, esegue ripetutamente un'istruzione incorporata e valuta una sequenza di espressioni di iterazione.

for_statement
    : 'for' '(' for_initializer? ';' for_condition? ';' for_iterator? ')'
      embedded_statement
    ;

for_initializer
    : local_variable_declaration
    | statement_expression_list
    ;

for_condition
    : boolean_expression
    ;

for_iterator
    : statement_expression_list
    ;

statement_expression_list
    : statement_expression (',' statement_expression)*
    ;

Il for_initializer, se presente, è costituito da un local_variable_declaration (§13.6.2) o da un elenco di statement_expression(§13.7) separati da virgole. L'ambito di una variabile locale dichiarata da un for_initializer è il for_initializer, for_condition, for_iterator e embedded_statement.

Il for_condition, se presente, sarà un boolean_expression (§12.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'istruzione continue ), 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'istruzione for .

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 valore falsecostante .

Il punto finale di un'istruzione for è raggiungibile se almeno una delle condizioni seguenti è vera:

  • L'istruzione for contiene un'istruzione raggiungibile break che esce dall'istruzione for .
  • L'istruzione for è raggiungibile e un for_condition è presente e non ha il valore truecostante .

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> IEnumeratore 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'interfaccia IEnumerable ,poiché System.Array implementa questa interfaccia. Il tipo di raccolta è l'interfaccia IEnumerable , il tipo di enumeratore è l'interfaccia IEnumerator e il tipo di iterazione è il tipo di elemento del tipo Xdi matrice .
  • Se il tipo X di espressione è dynamic presente una conversione implicita dall'espressione all'interfaccia IEnumerable (§10.2.10). Il tipo di raccolta è l'interfaccia IEnumerable e il tipo di enumeratore è l'interfaccia IEnumerator . Se l'identificatore var 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 appropriato GetEnumerator :
    • Eseguire la ricerca dei membri nel tipo X con identificatore GetEnumerator 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 del GetEnumerator 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'identificatore Current 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'identificatore MoveNext e senza argomenti di tipo. Se la ricerca del membro non produce alcuna corrispondenza, il risultato è un errore o il risultato è qualsiasi elemento ad eccezione di un gruppo di metodi, 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 è Ee il tipo di iterazione è il tipo della Current proprietà . La Current proprietà può includere il ref modificatore, nel qual caso l'espressione restituita è un variable_reference (§9.5) che è facoltativamente di sola lettura.
  • In caso contrario, verificare la presenza di un'interfaccia enumerabile:
    • Se tra tutti i tipi Tᵢ per cui è presente una conversione implicita da X a IEnumerable<Tᵢ>, esiste un tipo T univoco che T non dynamic è e per tutti gli altri Tᵢ è presente una conversione implicita da IEnumerable<T> a IEnumerable<Tᵢ>, il tipo di raccolta è l'interfaccia IEnumerable<T>, il tipo di enumeratore è l'interfaccia IEnumerator<T>e il tipo di iterazione è T.
    • In caso contrario, se è presente più di un tipo di questo tipo T, viene generato un errore e non vengono eseguiti altri passaggi.
    • In caso contrario, se è presente una conversione implicita da X all'interfaccia System.Collections.IEnumerable , il tipo di raccolta è questa interfaccia, il tipo di enumeratore è l'interfaccia System.Collections.IEnumeratore il tipo di iterazione è object.
    • In caso contrario, viene generato un errore e non vengono eseguiti altri passaggi.

I passaggi precedenti, se hanno esito positivo, producono in modo non ambiguo un tipo di raccolta , un tipo Cdi enumeratore e un tipo E di Titerazione , ref To 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 valore null, viene generata un'eccezione System.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 del while ciclo, verrebbe condiviso tra tutte le iterazioni e il relativo valore dopo il for ciclo sarebbe il valore finale, , 13che è ciò che la chiamata di f verrebbe stampata. Poiché ogni iterazione ha invece una propria variabile v, quella acquisita da f nella prima iterazione continuerà a contenere il valore 7, ovvero ciò che verrà stampato. Si noti che le versioni precedenti di C# dichiarate v all'esterno del while ciclo.

esempio finale

Il corpo del finally blocco viene costruito in base ai passaggi seguenti:

  • Se è presente una conversione implicita da E all'interfaccia System.IDisposable ,

    • Se E è un tipo di valore non nullable, la finally 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 in System.IDisposable non causerà l'esecuzione del e boxing.

  • In caso contrario, se E è un tipo sealed, la finally 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 come int, il tipo di iterazione di numbers.

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 che finally 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 , , whiledofor, o foreach più switchvicina.

break_statement
    : 'break' ';'
    ;

La destinazione di un'istruzione break è il punto finale dell'istruzione , forwhiledo, , o foreach più switchvicina. Se un'istruzione break non è racchiusa da un'istruzione switch, while, dofor, o foreach , si verifica un errore in fase di compilazione.

Quando più switchistruzioni , dowhile, 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 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 .
  • 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 , dofor, , o foreach più whilevicina.

continue_statement
    : 'continue' ';'
    ;

La destinazione di un'istruzione continue è il punto finale dell'istruzione incorporata dell'istruzione contenitore più whilevicina, do, for, o foreach . Se un'istruzione continue non è racchiusa da un'istruzione while, do, foro foreach , si verifica un errore in fase di compilazione.

Quando più whileistruzioni , do, foro foreach sono annidate tra loro, un'istruzione continue si applica solo all'istruzione più interna. Per trasferire il controllo tra più livelli di annidamento, verrà utilizzata un'istruzione goto (§13.10.4).

Un'istruzione continue non può uscire da un finally blocco (§13.11). Quando si verifica un'istruzione continue all'interno di un finally blocco, la destinazione dell'istruzione continue deve trovarsi all'interno dello stesso finally blocco; in caso contrario, si verifica un errore in fase di compilazione.

Un'istruzione continue viene eseguita come segue:

  • Se l'istruzione continue esce da uno o più 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 .
  • 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'esempio

class 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 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 .
  • 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 voidrestituito 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 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 di inclusione try .
  • 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.Exceptione 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 istruzione S, a partire dall'istruzione più try interna e terminando con l'istruzione più esterna try , vengono valutati i passaggi seguenti:

    • Se il blocco di racchiude il try punto di S generazione e se S ha una o più catch clausole, le catch clausole vengono esaminate in ordine di aspetto per individuare un gestore appropriato per l'eccezione. La prima catch clausola che specifica un tipo di eccezione (o un parametro di tipo T che in fase di esecuzione indica un tipo di eccezione ) in modo che il tipo Tdi runtime di E deriva da T 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 una catch clausola contiene un filtro di eccezione, tale catch clausola viene considerata una corrispondenza se il filtro eccezioni restituisce true. Una clausola generale catch (§13.11) è considerata una corrispondenza per qualsiasi tipo di eccezione. Se si trova una clausola corrispondente catch , la propagazione dell'eccezione viene completata trasferendo il controllo al blocco di tale catch clausola.
    • In caso contrario, se il try blocco o un catch blocco di racchiude il punto throw S e se S ha un finally blocco, il controllo viene trasferito al finally blocco. Se il finally blocco genera un'altra eccezione, l'elaborazione dell'eccezione corrente viene terminata. In caso contrario, quando il controllo raggiunge il punto finale del finally blocco, l'elaborazione dell'eccezione corrente viene continuata.
  • 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 voidrestituisce , 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 generale catch può essere utilizzata per intercettare tali eccezioni. Pertanto, una clausola generale catch è semanticamente diversa da quella che specifica il tipo System.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 generato e 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, continueo 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 continuebreak

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, gotoo return come risultato della propagazione di un'eccezione all'esterno dell'istruzione try . Se il controllo raggiunge il punto finale del finally blocco senza propagare un'eccezione, il controllo viene trasferito al punto finale dell'istruzione try .

Se viene generata un'eccezione durante l'esecuzione di un finally blocco e non viene intercettata all'interno dello stesso finally blocco, l'eccezione viene propagata all'istruzione di inclusione try successiva. Se è in corso la propagazione di un'altra eccezione, l'eccezione viene persa. Il processo di propagazione di un'eccezione è illustrato più avanti nella descrizione dell'istruzione throw (§13.10.6).

Esempio: nel codice seguente

public class Test
{
    static void Main()
    {
        try
        {
            Method();
        }
        catch (Exception ex) when (ExceptionFilter(ex))
        {
            Console.WriteLine("Catch");
        }

        bool ExceptionFilter(Exception ex)
        {
            Console.WriteLine("Filter");
            return true;
        }
    }

    static void Method()
    {
        try
        {
            throw new ArgumentException();
        }
        finally
        {
            Console.WriteLine("Finally");
        }
    }
}

il metodo Method genera un'eccezione. La prima azione consiste nell'esaminare le clausole di inclusione catch , eseguendo eventuali filtri di eccezione. Quindi, la clausola in Method viene eseguita prima del finally trasferimento del controllo alla clausola di corrispondenza catch 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 un catch blocco è raggiungibile.
  • Se è presente un finally blocco, il punto finale del finally 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 e TextReader implementano l'interfaccia IDisposable , l'esempio può usare using 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'istruzione finally try .
  • Si tratta di un errore in fase di compilazione per la visualizzazione di un'istruzione yield return in qualsiasi punto di un'istruzione try 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 associati finally non vengono eseguiti in questo momento.
  • Il MoveNext metodo dell'oggetto enumeratore restituisce true 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 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 di inclusione try .
  • Il controllo viene restituito al chiamante del blocco iteratore. Si tratta del metodo o Dispose del MoveNext 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.