Condividi tramite


12 espressioni

12.1 Generale

Un'espressione è una sequenza di operatori e operandi. Questa clausola definisce la sintassi, l'ordine di valutazione degli operandi e degli operatori e il significato delle espressioni.

12.2 Classificazioni di espressioni

12.2.1 Generale

Il risultato di un'espressione viene classificato come uno dei seguenti:

  • Un valore. Ogni valore ha un tipo associato.
  • Variabile. Se non diversamente specificato, una variabile viene tipizzata in modo esplicito e ha un tipo associato, ovvero il tipo dichiarato della variabile. Una variabile tipizzata in modo implicito non ha alcun tipo associato.
  • Valore letterale Null. Un'espressione con questa classificazione può essere convertita in modo implicito in un tipo di riferimento o in un tipo di valore nullable.
  • Funzione anonima. Un'espressione con questa classificazione può essere convertita in modo implicito in un tipo di delegato o un tipo di albero delle espressioni compatibile.
  • Tupla. Ogni tupla ha un numero fisso di elementi, ognuno con un'espressione e un nome di elemento di tupla facoltativo.
  • Accesso a una proprietà. Ogni accesso alle proprietà ha un tipo associato, ovvero il tipo della proprietà. Inoltre, un accesso alle proprietà può avere un'espressione di istanza associata. Quando viene richiamata una funzione di accesso a una proprietà dell'istanza, il risultato della valutazione dell'espressione di istanza diventa l'istanza rappresentata da this (§12.8.14).
  • Accesso a un indicizzatore. Ogni accesso dell'indicizzatore ha un tipo associato, ovvero il tipo di elemento dell'indicizzatore. Inoltre, un accesso indicizzatore ha un'espressione di istanza associata e un elenco di argomenti associato. Quando viene richiamata una funzione di accesso di un indicizzatore, il risultato della valutazione dell'espressione di istanza diventa l'istanza rappresentata da this (§12.8.14) e il risultato della valutazione dell'elenco di argomenti diventa l'elenco di parametri della chiamata.
  • Niente. Ciò si verifica quando l'espressione è una chiamata di un metodo con un tipo restituito di void. Un'espressione classificata come nulla è valida solo nel contesto di un statement_expression (§13.7) o come corpo di un lambda_expression (§12.19).

Per le espressioni che si verificano come sottoespressioni di espressioni più grandi, con le restrizioni indicate, il risultato può anche essere classificato come uno dei seguenti:

  • Uno spazio dei nomi (namespace). Un'espressione con questa classificazione può comparire solo come parte sinistra di un member_access (§12.8.7). In qualsiasi altro contesto, un'espressione classificata come "namespace" provoca un errore in fase di compilazione.
  • Tipo Un'espressione con questa classificazione può comparire solo come parte sinistra di un member_access (§12.8.7). In qualsiasi altro contesto, un'espressione classificata come tipo genera un errore in fase di compilazione.
  • Un gruppo di metodi, che è un set di metodi sovraccarichi risultanti da una ricerca di membro (§12.5). Un gruppo di metodi può avere un'espressione di istanza associata e un elenco di argomenti di tipo associato. Quando viene richiamato un metodo di istanza, il risultato della valutazione dell'espressione di istanza diventa l'istanza rappresentata da this (§12.8.14). Un gruppo di metodi è consentito in un invocation_expression (§12.8.10) o in un delegate_creation_expression (§12.8.17.6) e può essere convertito in modo implicito in un tipo delegato compatibile (§10.8). In qualsiasi altro contesto, un'espressione classificata come gruppo di metodi causa un errore in fase di compilazione.
  • Accesso a un evento. Ogni accesso agli eventi ha un tipo associato, ovvero il tipo dell'evento. Inoltre, un accesso a un evento può avere un'espressione di istanza associata. Un accesso agli eventi può essere visualizzato come operando sinistro degli operatori += e -= (§12.21.5). In qualsiasi altro contesto, un'espressione classificata come accesso a un evento causa un errore in fase di compilazione. Quando viene invocato un accessor di accesso a un evento di istanza, il risultato della valutazione dell'espressione di istanza diventa l'istanza rappresentata da this (§12.8.14).
  • Un'espressione throw, che può essere usata in diversi contesti per lanciare un'eccezione in un'espressione. Un'espressione throw può essere convertita in qualsiasi tipo tramite una conversione implicita.

Un accesso alle proprietà o all'indicizzatore viene sempre riclassificato come valore eseguendo una chiamata della funzione di accesso get o della funzione di accesso set. L'accessorio particolare è determinato dal contesto dell'accesso alla proprietà o all'indicizzatore: se l'accesso è la destinazione di un'assegnazione, viene richiamato il set accessor per assegnare un nuovo valore (§12.21.2). In caso contrario, il metodo accessor get viene richiamato per ottenere il valore corrente (§12.2.2).

Una funzione di accesso all'istanza è un accesso alle proprietà in un'istanza, un accesso a un evento in un'istanza o un accesso dell'indicizzatore.

12.2.2 Valori delle espressioni

La maggior parte dei costrutti che coinvolgono un'espressione richiede infine che l'espressione denoti un valore . In questi casi, se l'espressione effettiva indica uno spazio dei nomi, un tipo, un gruppo di metodi o nulla, si verifica un errore in fase di compilazione. Tuttavia, se l'espressione indica un accesso a una proprietà, un accesso a un indicizzatore o una variabile, il valore della proprietà, dell'indicizzatore o della variabile viene sostituito in modo implicito:

  • Il valore di una variabile è semplicemente il valore attualmente archiviato nella posizione di archiviazione identificata dalla variabile. Una variabile deve essere considerata sicuramente assegnata (§9,4) prima che il valore possa essere ottenuto oppure si verifica un errore in fase di compilazione.
  • Il valore di un'espressione di accesso alla proprietà viene ottenuto richiamando l'accessor get della proprietà. Se la proprietà non dispone di una funzione di accesso get, si verifica un errore in fase di compilazione. In caso contrario, viene eseguita una chiamata a un membro della funzione (§12.6.6) e il risultato della chiamata diventa il valore dell'espressione di accesso alle proprietà.
  • Il valore di un'espressione di accesso dell'indicizzatore viene ottenuto richiamando la funzione di accesso get dell'indicizzatore. Se l'indicizzatore non dispone di una funzione di accesso get, si verifica un errore in fase di compilazione. In caso contrario, viene eseguita una chiamata a un membro di funzione (§12.6.6) con l'elenco di argomenti associato all'espressione di accesso dell'indicizzatore e il risultato della chiamata diventa il valore dell'espressione di accesso dell'indicizzatore.
  • Il valore di un'espressione di tupla viene ottenuto applicando una conversione di tupla implicita (§10.2.13) al tipo dell'espressione di tupla. È un errore ottenere il valore di un'espressione tupla che non ha un tipo.

12.3 Associazione statica e dinamica

12.3.1 Generale

Binding è il processo di determinazione del riferimento a un'operazione, in base al tipo o al valore delle espressioni (argomenti, operandi, ricevitori). Ad esempio, l'associazione di una chiamata al metodo viene determinata in base al tipo di ricevitore e argomenti. L'associazione di un operatore viene determinata in base al tipo dei relativi operandi.

In C# l'associazione di un'operazione viene in genere determinata in fase di compilazione, in base al tipo in fase di compilazione delle relative sottoespressioni. Analogamente, se un'espressione contiene un errore, l'errore viene rilevato e segnalato in fase di compilazione. Questo approccio è noto come associazione statica .

Tuttavia, se un'espressione è un 'espressione dinamica (ad esempio, ha il tipo dynamic) indica che qualsiasi associazione a cui fa parte deve essere basata sul tipo di runtime anziché sul tipo in cui è in fase di compilazione. Il legame di tale operazione è quindi posticipato fino al momento in cui l'operazione deve essere eseguita nel corso dell'esecuzione del programma. È noto come binding dinamico.

Quando un'operazione è associata dinamicamente, viene eseguito un controllo minimo o nullo in fase di compilazione. Se invece l'associazione al runtime ha esito negativo, gli errori vengono segnalati come eccezioni in fase di esecuzione.

Le operazioni seguenti in C# sono soggette al binding:

  • Accesso ai membri: e.M
  • Chiamata al metodo: e.M(e₁,...,eᵥ)
  • Chiamata del delegato: e(e₁,...,eᵥ)
  • Accesso all'elemento: e[e₁,...,eᵥ]
  • Creazione di oggetti: nuovo C(e₁,...,eᵥ)
  • Operatori unari sovraccaricati: +, -, ! (solo negazione logica), ~, ++, --, true, false
  • Operatori binari sovraccaricati: +, -, *, /, %, &, &&, |, ||, ??, ^, <<, >>, ==, !=, >, <, >=, <=
  • Operatori di assegnazione: =, = ref, +=, -=, *=, /=, %=, &=, |=, ^=, <<=, >>=
  • Conversioni implicite ed esplicite

Quando non sono coinvolte espressioni dinamiche, C# usa per impostazione predefinita l'associazione statica, il che significa che i tipi in fase di compilazione di sottoespressioni vengono usati nel processo di selezione. Tuttavia, quando una delle sottoespressioni nelle operazioni elencate in precedenza è un'espressione dinamica, l'operazione viene invece associata dinamicamente.

Si tratta di un errore in fase di compilazione se un'invocazione del metodo è vincolata dinamicamente e uno dei parametri, incluso il ricevitore, è un parametro di input.

12.3.2 Tempo di Associazione

L'associazione statica viene eseguita in fase di compilazione, mentre l'associazione dinamica avviene in fase di esecuzione. Nelle sottoclause seguenti, il termine tempo di binding si riferisce alla fase di compilazione o alla fase di run-time, a seconda del momento in cui si verifica l'associazione.

esempio: di seguito vengono illustrate le nozioni di associazione statica e dinamica e del tempo di associazione:

object o = 5;
dynamic d = 5;
Console.WriteLine(5); // static binding to Console.WriteLine(int)
Console.WriteLine(o); // static binding to Console.WriteLine(object)
Console.WriteLine(d); // dynamic binding to Console.WriteLine(int)

Le prime due chiamate sono staticamente vincolate: l'overload di Console.WriteLine viene selezionato in base al tipo del loro argomento in fase di compilazione. Di conseguenza, il tempo di associazione viene in fase di compilazione.

La terza chiamata è associata dinamicamente: l'overload di Console.WriteLine viene selezionato in base al tipo di runtime del relativo argomento. Ciò si verifica perché l'argomento è un'espressione dinamica e il suo tipo in fase di compilazione è dinamico. Quindi, il tempo di associazione per la terza chiamata è tempo di esecuzione.

esempio finale

12.3.3 Binding dinamico

Questa sottochiave è informativa.

L'associazione dinamica consente ai programmi C# di interagire con oggetti dinamici, ad esempio oggetti che non seguono le normali regole del sistema di tipi C#. Gli oggetti dinamici possono essere oggetti di altri linguaggi di programmazione con sistemi di tipi diversi oppure oggetti configurati a livello di codice per implementare la propria semantica di associazione per operazioni diverse.

Il meccanismo mediante il quale un oggetto dinamico implementa la propria semantica è definito dall'implementazione. Un'interfaccia specifica, di nuovo definita dall'implementazione, viene implementata da oggetti dinamici per segnalare al runtime C# che hanno una semantica speciale. Pertanto, ogni volta che le operazioni su un oggetto dinamico vengono collegate dinamicamente, la loro semantica di associazione, anziché quella di C# come specificato in questa specifica, assume il controllo.

Anche se lo scopo dell'associazione dinamica è consentire l'interoperabilità con oggetti dinamici, C# consente l'associazione dinamica su tutti gli oggetti, indipendentemente dal fatto che siano dinamici o meno. Ciò consente un'integrazione più fluida di oggetti dinamici, in quanto i risultati delle operazioni su di essi potrebbero non essere oggetti dinamici, ma sono ancora di un tipo sconosciuto al programmatore in fase di compilazione. Inoltre, l'associazione dinamica può aiutare a eliminare il codice basato su reflection soggetto a errori anche quando non sono coinvolti oggetti dinamici.

12.3.4 Tipi di sottoespressioni

Quando un'operazione è associata in modo statico, il tipo di una sottoespressione (ad esempio, un ricevitore e un argomento, un indice o un operando) viene sempre considerato il tipo in fase di compilazione di tale espressione.

Quando un'operazione è associata dinamicamente, il tipo di una sottoespressione viene determinato in modi diversi a seconda del tipo in fase di compilazione della sottoespressione:

  • Una sottoespressione del tipo dinamico in fase di compilazione è considerata avere il tipo del valore effettivo a cui l'espressione si valuta in fase di esecuzione.
  • Una sottoespressione il cui tipo in fase di compilazione è un parametro di tipo viene considerata del tipo a cui il parametro di tipo è legato in fase di esecuzione.
  • In caso contrario, la sottoespressione viene considerata come avesse il suo tipo al momento della compilazione.

12.4 Operatori

12.4.1 Generale

Le espressioni vengono costruite da operandi e operatori. Gli operatori di un'espressione indicano quali operazioni applicare agli operandi.

Esempio: esempi di operatori includono +, -, *, /e new. Esempi di operandi includono valori letterali, campi, variabili locali ed espressioni. esempio finale

Esistono tre tipi di operatori:

  • Operatori unari. Gli operatori unari accettano un operando e usano la notazione del prefisso (ad esempio –x) o la notazione postfissa (ad esempio x++).
  • Operatori binari. Gli operatori binari accettano due operandi e tutti usano la notazione infix (ad esempio x + y).
  • Operatore Ternario. Esiste un solo operatore ternario, ?:, accetta tre operandi e usa la notazione infix (c ? x : y).

L'ordine di valutazione degli operatori in un'espressione è determinato dalla precedenza e dalla associatività degli operatori (§12.4.2).

Gli operandi in un'espressione vengono valutati da sinistra a destra.

esempio: in F(i) + G(i++) * H(i), il metodo F viene chiamato usando il valore precedente di i, quindi viene chiamato il metodo G con il valore precedente di ie, infine, viene chiamato il metodo H con il nuovo valore di i. Questo è separato da e non correlato alla precedenza dell'operatore. esempio finale

Alcuni operatori possono essere sovraccaricati. Overload degli operatori (§12.4.3) consente di specificare le implementazioni degli operatori definiti dall'utente per le operazioni in cui uno o entrambi gli operandi sono di un tipo di classe o struct definito dall'utente.

12.4.2 Precedenza e associatività degli operatori

Quando un'espressione contiene più operatori, la precedenza degli operatori controlla l'ordine in cui vengono valutati i singoli operatori.

Nota: ad esempio, l'espressione x + y * z viene valutata come x + (y * z) perché l'operatore * ha una precedenza maggiore rispetto all'operatore binario +. nota finale

La precedenza di un operatore viene stabilita dalla definizione della produzione grammaticale associata.

Nota: ad esempio, un additive_expression è costituito da una sequenza di multiplicative_expressionseparati da operatori + o -, assegnando così agli operatori + e - la precedenza inferiore rispetto agli operatori *, /e %. nota finale

Nota: la tabella seguente riepiloga tutti gli operatori in ordine di precedenza dal più alto al più basso:

Sottoclause Categoria operatori
§12.8 Primario x.y x?.y f(x) a[x] a?[x] x++ x-- x! new typeof default checked unchecked delegate stackalloc
§12.9 Unario + - !x ~ ++x --x (T)x await x
§12.10 Moltiplicativo * / %
§12.10 Additivo + -
§12.11 Spostamento << >>
§12.12 Test relazionali e di tipi < > <= >= is as
§12.12 Uguaglianza == !=
§12.13 E logico &
§12.13 Operatore logico XOR ^
§12.13 OR logico \|
§12.14 E condizionale &&
§12.14 OR condizionale \|\|
§12.15 e §12.16 Operatore di coalescenza dei valori null ed espressione throw ?? throw x
§12.18 Condizionale ?:
§12.21 e §12.19 Assegnazione ed espressione lambda = = ref *= /= %= += -= <<= >>= &= ^= \|= =>

nota finale

Quando si verifica un operando tra due operatori con la stessa precedenza, la associatività degli operatori controlla l'ordine in cui vengono eseguite le operazioni:

  • Ad eccezione degli operatori di assegnazione e dell'operatore di coalescenza nulla, tutti gli operatori binari sono associativi a sinistra, ovvero, le operazioni vengono eseguite da sinistra a destra.

    esempio: x + y + z viene valutato come (x + y) + z. esempio finale

  • Gli operatori di assegnazione, l'operatore null di unione e l'operatore condizionale (?:) sono associativa a destra, ovvero le operazioni vengono eseguite da destra a sinistra.

    esempio: x = y = z viene valutato come x = (y = z). esempio finale

La precedenza e l'associatività possono essere controllate usando parentesi.

esempio: x + y * z moltiplica prima y per z e quindi aggiunge il risultato a x, ma (x + y) * z aggiunge prima x e y e quindi moltiplica il risultato per z. esempio finale

12.4.3 Sovraccarico degli operatori

Tutti gli operatori unari e binari hanno implementazioni predefinite. Inoltre, le implementazioni definite dall'utente possono essere introdotte includendo dichiarazioni di operatore (§15.10) in classi e struct. Le implementazioni degli operatori definite dall'utente hanno sempre la precedenza sulle implementazioni degli operatori predefinite: solo se non esistono implementazioni di operatori definite dall'utente applicabili, verranno considerate le implementazioni predefinite degli operatori, come descritto in §12.4.4 e §12.4.5.

Gli operatori unari sovraccaricabili sono:

+ - ! (solo negazione logica) ~ ++ -- true false

Nota: sebbene true e false non vengano usati in modo esplicito nelle espressioni (e pertanto non sono inclusi nella tabella di precedenza in §12.4.2), sono considerati operatori perché vengono richiamati in diversi contesti di espressione : espressioni booleane (§12.24) ed espressioni che coinvolgono l'espressione condizionale (§12.18) e gli operatori logici condizionali (§12.14). nota finale

Nota: l'operatore null-forgiving (postfix !, §12.8.9) non è un operatore sovraccaricabile. nota finale

Gli operatori binari sovraccaricabili sono:

+  -  *  /  %  &  |  ^  <<  >>  ==  !=  >  <  <=  >=

È possibile sovraccaricare solo gli operatori elencati in precedenza. In particolare, non è possibile eseguire il sovraccarico dell'accesso ai membri, la chiamata al metodo o gli operatori di =, &&, ||, ??, ?:, =>, checked, unchecked, new, typeof, default, ase is.

Quando si sovraccarica un operatore binario, anche l'operatore di assegnazione composta corrispondente viene implicitamente sovraccaricato.

Esempio: un sovraccarico dell'operatore * è anche un sovraccarico dell'operatore *=. Questo articolo è descritto più avanti in §12.21. esempio finale

L'operatore di assegnazione stesso (=) non può essere sovraccaricato. Un'assegnazione esegue sempre una semplice memorizzazione di un valore in una variabile (§12.21.2).

Le operazioni di cast, come (T)x, vengono sovraccaricate attraverso conversioni definite dall'utente (§10,5).

Nota: le conversioni definite dall'utente non influiscono sul comportamento degli operatori is o as. nota finale

L'accesso all'elemento, ad esempio a[x], non è considerato un operatore sovraccaricabile. L'indicizzazione definita dall'utente è invece supportata tramite indicizzatori (§15.9).

Nelle espressioni viene fatto riferimento agli operatori usando la notazione dell'operatore e nelle dichiarazioni viene fatto riferimento agli operatori usando la notazione funzionale. Nella tabella seguente viene illustrata la relazione tra operatore e notazioni funzionali per gli operatori unari e binari. Nella prima voce, «op» denota qualsiasi operatore unario di prefisso sovraccaricabile. Nella seconda voce «op» indica gli operatori di postfissi unari ++ e --. Nella terza voce, «op» indica qualsiasi operatore binario sovraccaricabile.

Nota: Per un esempio di sovraccarico degli operatori ++ e --, vedere §15.10.2. nota finale

notazione dell'operatore notazione funzionale
«op» x operator «op»(x)
x «op» operator «op»(x)
x «op» y operator «op»(x, y)

Le dichiarazioni di operatore definite dall'utente richiedono sempre che almeno uno dei parametri sia della classe o del tipo struct che contiene la dichiarazione dell'operatore.

Nota: pertanto, non è possibile che un operatore definito dall'utente abbia la stessa firma di un operatore predefinito. nota finale

Le dichiarazioni di operatore definite dall'utente non possono modificare la sintassi, la precedenza o l'associatività di un operatore.

esempio: l'operatore / è sempre un operatore binario, ha sempre il livello di precedenza specificato in §12.4.2ed è sempre associativo a sinistra. esempio finale

Nota: sebbene sia possibile che un operatore definito dall'utente esegua qualsiasi calcolo, le implementazioni che producono risultati diversi da quelli previsti in modo intuitivo sono fortemente sconsigliati. Ad esempio, un'implementazione dell'operatore == deve confrontare i due operandi per verificarne l'uguaglianza e restituire un risultato bool appropriato. nota finale

Le descrizioni dei singoli operatori in §12.9 fino a §12.21 specificano le implementazioni predefinite degli operatori e le regole aggiuntive applicabili a ogni operatore. Le descrizioni usano i termini risoluzione dell'overload dell'operatore unario, risoluzione dell'overload dell'operatore binario, promozione numericae definizioni di operatori elevati, le cui descrizioni si trovano nei seguenti sottocommi.

12.4.4 Risoluzione dell'overload degli operatori unari

Un'operazione del form «op» x o x «op», dove «op» è un operatore unario sovraccaricabile e x è un'espressione di tipo X, viene elaborata come segue:

  • Il set di operatori candidati definiti dall'utente forniti da X per l'operazione operator «op»(x) viene determinato usando le regole di §12.4.6.
  • Se il set di operatori candidati definiti dall'utente non è vuoto, questo diventa il set di operatori candidati per l'operazione. In caso contrario, le implementazioni binarie predefinite operator «op», incluse le loro forme sollevate, costituiscono l'insieme degli operatori candidati per l'operazione. Le implementazioni predefinite di un determinato operatore vengono specificate nella descrizione dell'operatore. Gli operatori predefiniti forniti da un tipo enum o delegato sono inclusi in questo set solo quando il tipo al momento del binding, o il tipo sottostante se è un tipo nullable, di uno degli operandi è il tipo enum o delegato.
  • Le regole di risoluzione dell'overload di §12.6.4 vengono applicate al set di operatori candidati per selezionare l'operatore migliore rispetto all'elenco di argomenti (x)e questo operatore diventa il risultato del processo di risoluzione dell'overload. Se la risoluzione dell'overload non riesce a selezionare un singolo operatore migliore, si verifica un errore al momento del binding.

12.4.5 Risoluzione dell'overload dell'operatore binario

Un'operazione della forma x «op» y, dove «op» è un operatore binario sovraccaricabile, x è un'espressione di tipo Xe y è un'espressione di tipo Y, viene elaborata come segue:

  • Si determina il set di operatori candidati definiti dall'utente forniti da X e Y per l'operazione operator «op»(x, y). Il set è costituito dall'unione degli operatori candidati forniti da X e dagli operatori candidati forniti da Y, ognuno determinato utilizzando le regole di §12.4.6. Per il set combinato, i candidati vengono uniti come segue:
    • Se X e Y sono convertibili tramite identità o se X e Y sono derivati da un tipo di base comune, gli operatori candidati condivisi si verificano solo una volta nel set combinato.
    • Se è presente una conversione di identità tra X e Y, un operatore «op»Y fornito da Y ha lo stesso tipo di restituzione di un «op»X fornito da X e i tipi di operando di «op»Y hanno una conversione di identità nei tipi di operando corrispondenti di «op»X, quindi si verifica solo «op»X nell'insieme.
  • Se il set di operatori candidati definiti dall'utente non è vuoto, questo diventa il set di operatori candidati per l'operazione. In caso contrario, le implementazioni binarie predefinite operator «op», incluse le loro forme sollevate, costituiscono l'insieme degli operatori candidati per l'operazione. Le implementazioni predefinite di un determinato operatore vengono specificate nella descrizione dell'operatore. Per gli operatori predefiniti di enumerazione e delegato, gli unici operatori presi in considerazione sono quelli forniti da un tipo enum o delegato che corrisponde al tipo determinato in fase di compilazione di uno degli operandi.
  • Le regole di risoluzione dell'overload di §12.6.4 vengono applicate al set di operatori candidati per selezionare l'operatore migliore rispetto all'elenco di argomenti (x, y)e questo operatore diventa il risultato del processo di risoluzione dell'overload. Se la risoluzione dell'overload non riesce a selezionare un singolo operatore migliore, si verifica un errore al momento del binding.

Operatori candidati definiti dall'utente

Dato un tipo T e un'operazione operator «op»(A), dove «op» è un operatore di overload e A è un elenco di argomenti, il set di operatori candidati definiti dall'utente forniti da T per l'operatore «op»(A) viene determinato come segue:

  • Determinare il tipo T₀. Se T è un tipo valore nullable, T₀ è il tipo sottostante; altrimenti, T₀ è uguale a T.
  • Per tutte le dichiarazioni operator «op» in T₀ e tutte le forme elevate di tali operatori, se almeno un operatore è applicabile (§12.6.4.2) rispetto all'elenco di argomenti A, l'insieme degli operatori candidati consiste in tutti gli operatori applicabili in T₀.
  • In caso contrario, se T₀ è object, l'insieme di operatori candidati è vuoto.
  • In caso contrario, il set di operatori candidati forniti da T₀ è il set di operatori candidati forniti dalla classe base diretta di T₀o dalla classe base effettiva di T₀ se T₀ è un parametro di tipo.

12.4.7 Promozioni numeriche

12.4.7.1 Generale

Questa sottochiave è informativa.

§12.4.7 e le relative sottoclause sono un riepilogo dell'effetto combinato di:

  • le regole per le conversioni numeriche implicite (§10.2.3);
  • le regole per una migliore conversione (§12.6.4.7); e
  • operatori aritmetici disponibili (§12.10), relazionali (§12.12) e logici integrali (§12.13.2).

La promozione numerica consiste nell'eseguire automaticamente determinate conversioni implicite degli operandi degli operatori numerici unari e binari predefiniti. La promozione numerica non è un meccanismo distinto, ma piuttosto un effetto dell'applicazione della risoluzione dell'overload agli operatori predefiniti. La promozione numerica in particolare non influisce sulla valutazione degli operatori definiti dall'utente, anche se gli operatori definiti dall'utente possono essere implementati per presentare effetti simili.

Come esempio di promozione numerica, considerare le implementazioni predefinite dell'operatore binario *:

int operator *(int x, int y);
uint operator *(uint x, uint y);
long operator *(long x, long y);
ulong operator *(ulong x, ulong y);
float operator *(float x, float y);
double operator *(double x, double y);
decimal operator *(decimal x, decimal y);

Quando le regole di risoluzione dell'overload (§12.6.4) vengono applicate a questo set di operatori, l'effetto consiste nel selezionare il primo degli operatori per i quali esistono conversioni implicite dai tipi degli operandi.

Esempio: Per l'operazione b * s, dove b è un byte e s è un short, la risoluzione dell'overload seleziona operator *(int, int) come miglior operatore. Di conseguenza, l'effetto è che b e s vengono convertiti in inte il tipo del risultato è int. Analogamente, per l'operazione i * d, dove i è un int e d è un double, la risoluzione overload seleziona operator *(double, double) come operatore migliore. esempio finale

Fine del testo informativo.

12.4.7.2 Promozioni numeriche unarie

Questa sottochiave è informativa.

La promozione numerica unaria si applica agli operandi degli operatori unari predefiniti +, -e ~. La promozione numerica unaria consiste semplicemente nella conversione di operandi di tipo sbyte, byte, short, ushorto char nel tipo int. Inoltre, per l'operatore unario –, la promozione numerica unaria converte gli operandi di tipo uint in tipo long.

Fine del testo informativo.

12.4.7.3 Promozioni numeriche binarie

Questa sottochiave è informativa.

La promozione numerica binaria viene eseguita per gli operandi degli operatori binari predefiniti +, -, *, /, %, &, |, ^, ==, !=, >, <, >=e <=. La promozione numerica binaria converte in modo implicito entrambi gli operandi in un tipo comune che, nel caso degli operatori non relazionali, diventa anche il tipo di risultato dell'operazione. La promozione numerica binaria consiste nell'applicare le seguenti regole, nell'ordine in cui appaiono qui:

  • Se uno degli operandi è di tipo decimal, l'altro operando viene convertito nel tipo decimalo si verifica un errore di binding se l'altro operando è di tipo float o double.
  • In caso contrario, se uno degli operandi è di tipo double, l'altro operando viene convertito nel tipo double.
  • In caso contrario, se uno degli operandi è di tipo float, l'altro operando viene convertito nel tipo float.
  • In caso contrario, se uno degli operandi è di tipo ulong, l'altro operando viene convertito nel tipo ulongo si verifica un errore di binding se l'altro operando è di type sbyte, short, into long.
  • In caso contrario, se uno degli operandi è di tipo long, l'altro operando viene convertito nel tipo long.
  • In caso contrario, se uno degli operandi è di tipo uint e l'altro operando è di tipo sbyte, shorto int, entrambi gli operandi vengono convertiti nel tipo long.
  • In caso contrario, se uno degli operandi è di tipo uint, l'altro operando viene convertito nel tipo uint.
  • In caso contrario, entrambi gli operandi vengono convertiti in tipo int.

Nota: la prima regola non consente alcuna operazione che combina il tipo di decimal con i tipi double e float. La regola segue dal fatto che non vi sono conversioni implicite tra il tipo di decimal e i tipi double e float. nota finale

Nota: si noti anche che non è possibile che un operando sia di tipo ulong quando l'altro operando è di un tipo integrale con segno. Il motivo è che non esiste alcun tipo integrale che può rappresentare l'intera gamma di ulong e i tipi integrali firmati. nota finale

In entrambi i casi precedenti, un'espressione cast può essere usata per convertire in modo esplicito un operando in un tipo compatibile con l'altro operando.

Esempio: nel codice seguente

decimal AddPercent(decimal x, double percent) =>
    x * (1.0 + percent / 100.0);

Si verifica un errore di binding-time perché non è possibile moltiplicare un decimal per un double. L'errore viene risolto convertendo in modo esplicito il secondo operando in decimal, come indicato di seguito:

decimal AddPercent(decimal x, double percent) =>
    x * (decimal)(1.0 + percent / 100.0);

esempio finale

Fine del testo informativo.

12.4.8 Operatori lifted

operatori sollevati consentono l'uso di operatori predefiniti e definiti dall'utente che operano su tipi valore non annullabile con anche le forme annullabili di tali tipi. Gli operatori lifted vengono costruiti da operatori predefiniti e definiti dall'utente che soddisfano determinati requisiti, come illustrato di seguito.

  • Per gli operatori unari +, ++, -, --, !(negazione logica) e ~, esiste una forma "lifted" dell'operatore se sia l'operando che i tipi di risultato sono tipi di valore non annullabili. La forma sollevata viene costruita aggiungendo un singolo modificatore ? ai tipi di operando e risultato. L'operatore sollevato produce un valore null se l'operando è null. In caso contrario, l'operatore elevato rimuove il wrapping dell'operando, applica l'operatore sottostante e riavvolge il risultato.
  • Per gli operatori binari +, -, *, /, %, &, |, ^, <<e >>, esiste una forma sollevata di un operatore se i tipi di operando e risultato sono tutti tipi di valore non nullabile. La forma sollevata viene costruita aggiungendo un singolo ? modificatore a ciascun operando e tipo di risultato. L'operatore 'lifted' produce un valore null se uno o entrambi gli operandi sono null (un'eccezione è rappresentata dagli operatori & e | del tipo bool?, come descritto in §12.13.5). In caso contrario, l'operatore sollevato rimuove l'involucro degli operandi, applica l'operatore sottostante e avvolge il risultato.
  • Per gli operatori di uguaglianza == e !=, esiste una forma elevata di un operatore se i tipi degli operandi sono entrambi tipi valore non annullabili e se il tipo di risultato è bool. La forma sollevata viene costruita aggiungendo un singolo modificatore ? a ogni tipo di operando. L'operatore lifted considera due valori null uguali e un valore null diverso da qualsiasi valore nonnull. Se entrambi gli operandi non sononull, l'operatore elevato rimuove il wrapping degli operandi e applica l'operatore sottostante per produrre il risultato bool.
  • Per gli operatori relazionali <, >, <=e >=, esiste una forma sollevata di un operatore se entrambi i tipi degli operandi sono tipi valore non annullabili e se il tipo di risultato è bool. La forma sollevata viene costruita aggiungendo un singolo modificatore ? a ogni tipo di operando. L'operatore sollevato produce il valore false se uno o entrambi gli operandi sono null. In caso contrario, l'operatore alzato svolge gli operandi e applica l'operatore sottostante per ottenere il risultato bool.

12.5 Ricerca membri

12.5.1 Generale

La ricerca di un membro è il processo in base al quale viene determinato il significato di un nome nel contesto di un tipo. Una ricerca membro può essere eseguita come parte della valutazione di un simple_name (§12.8.4) o di un member_access (§12.8.7) in un'espressione. Se il simple_name o il member_access si verifica come il espressione_primaria di un invocation_expression (§12.8.10.2), il membro viene detto richiamato.

Se un membro è un metodo o un evento, o se è una costante, un campo o una proprietà di un tipo delegato (§20) o il tipo dynamic (§8.2.4), il membro viene detto invocabile.

La ricerca dei membri considera non solo il nome di un membro, ma anche il numero di parametri di tipo del membro e se il membro è accessibile. Ai fini della ricerca dei membri, i metodi generici e i tipi generici annidati hanno il numero di parametri di tipo indicati nelle rispettive dichiarazioni e tutti gli altri membri hanno parametri di tipo zero.

Una ricerca di un membro con il nome N con argomenti del tipo K in un tipo T viene elaborata come segue:

  • In primo luogo, viene determinato un set di membri accessibili denominati N:
    • Se T è un parametro di tipo, il set è l'unione dei set di membri accessibili denominati N in ognuno dei tipi specificati come vincolo primario o vincolo secondario (§15.2.5) per T, insieme al set di membri accessibili denominati N in object.
    • In caso contrario, il set è costituito da tutti i membri accessibili (§7.5) denominati N in T, inclusi i membri ereditati e i membri accessibili denominati N in object. Se T è un tipo costruito, l'insieme dei membri viene ottenuto sostituendo gli argomenti di tipo come descritto in §15.3.3. I membri che includono un modificatore override vengono esclusi dal set.
  • Successivamente, se K è zero, vengono rimossi tutti i tipi annidati le cui dichiarazioni includono parametri di tipo. Se K non è zero, vengono rimossi tutti i membri con un numero diverso di parametri di tipo. Quando K è zero, i metodi con parametri di tipo non vengono rimossi, poiché il processo di inferenza del tipo (§12.6.3) potrebbe essere in grado di dedurre gli argomenti del tipo.
  • Successivamente, se il membro viene richiamato, tutti i membri non richiamabili vengono rimossi dal set.
  • Successivamente, i membri nascosti da altri membri vengono rimossi dal set. Per ogni membro S.M nel set, dove S è il tipo in cui il membro M è dichiarato, vengono applicate le regole seguenti.
    • Se M è un membro costante, campo, proprietà, evento o enumerazione, tutti i membri dichiarati in un tipo di base di S vengono rimossi dal set.
    • Se M è una dichiarazione di tipo, tutti i tipi non dichiarati in un tipo di base di S vengono rimossi dal set e tutte le dichiarazioni di tipo con lo stesso numero di parametri di tipo M dichiarati in un tipo di base di S vengono rimosse dal set.
    • Se M è un metodo, tutti i membri non di metodo dichiarati in un tipo di base di S vengono rimossi dal set.
  • Successivamente, i membri dell'interfaccia nascosti dai membri della classe vengono rimossi dal set. Questo passaggio ha effetto solo se T è un parametro di tipo e T ha sia una classe base efficace diversa da object che un set di interfacce non vuoto (§15.2.5). Per ogni membro S.M nel set, dove S è il tipo in cui il membro M viene dichiarato, vengono applicate le regole seguenti se S è una dichiarazione di classe diversa da object:
    • Se M è una dichiarazione costante, campo, proprietà, evento, membro di enumerazione o tipo, tutti i membri dichiarati in una dichiarazione di interfaccia vengono rimossi dal set.
    • Se M è un metodo, tutti i membri non di metodo dichiarati in una dichiarazione di interfaccia vengono rimossi dal set e tutti i metodi con la stessa firma di M dichiarata in una dichiarazione di interfaccia vengono rimossi dal set.
  • Infine, dopo aver rimosso i membri nascosti, viene determinato il risultato della ricerca:
    • Se il set è costituito da un singolo membro che non è un metodo, questo membro è il risultato della ricerca.
    • In caso contrario, se il set contiene solo metodi, questo gruppo di metodi è il risultato della ricerca.
    • In caso contrario, la ricerca è ambigua e si verifica un errore di data e ora di associazione.

Per le ricerche dei membri in tipi diversi dai parametri di tipo e dalle interfacce, e le ricerche dei membri nelle interfacce con stretta ereditarietà singola (ogni interfaccia nella catena di ereditarietà ha esattamente zero o una sola interfaccia di base diretta), l'effetto delle regole di ricerca è semplicemente che i membri derivati nascondono i membri di base con lo stesso nome o firma. Queste ricerche con ereditarietà singola non sono mai ambigue. Le ambiguità che possono verificarsi nelle ricerche dei membri nelle interfacce di ereditarietà multipla sono descritte in §18.4.6.

Nota: questa fase rappresenta solo un tipo di ambiguità. Se la ricerca di un membro restituisce un gruppo di metodi, ulteriori usi di tale gruppo di metodi potrebbero fallire a causa di ambiguità, ad esempio come descritto in §12.6.4.1 e §12.6.6.2. nota finale

12.5.2 Tipi di base

Ai fini della ricerca dei membri, un tipo T è considerato avere i seguenti tipi di base:

  • Se T è object o dynamic, T non ha alcun tipo di base.
  • Se T è un enum_type, i tipi di base di T sono i tipi di classe System.Enum, System.ValueTypee object.
  • Se T è un struct_type, i tipi di base di T sono i tipi di classe System.ValueType e object.

    Nota: un nullable_value_type è un struct_type (§8.3.1). nota finale

  • Se T è un class_type, i tipi di base di T sono le classi base di T, incluso il tipo di classe object.
  • Se T è un interface_type, i tipi di base di T sono le interfacce di base di T e il tipo di classe object.
  • Se T è un array_type, i tipi di base di T sono i tipi di classe System.Array e object.
  • Se T è un delegate_type, i tipi di base di T sono i tipi di classe System.Delegate e object.

12.6 Membri della funzione

12.6.1 Generale

I membri funzionali sono membri che contengono istruzioni eseguibili. Le funzioni membro sono sempre membri di tipi e non possono essere membri di namespace. C# definisce le categorie di membri della funzione seguenti:

  • Metodi
  • Proprietà
  • Avvenimenti
  • Indicizzatori
  • Operatori definiti dall'utente
  • Costruttori di istanza
  • Costruttori statici
  • Finalizzatori

Ad eccezione dei finalizzatori e dei costruttori statici (che non possono essere richiamati in modo esplicito), le istruzioni contenute nei membri della funzione vengono eseguite tramite chiamate ai membri della funzione. La sintassi effettiva per la scrittura di una chiamata a un membro di funzione dipende dalla categoria di membri della funzione specifica.

L'elenco di argomenti (§12.6.2) di una chiamata a un membro di funzione fornisce valori effettivi o riferimenti a variabili per i parametri del membro della funzione.

Le chiamate dei metodi generici possono usare l'inferenza dei tipi per determinare l'insieme degli argomenti di tipo da trasmettere al metodo. Questo processo è descritto in §12.6.3.

Le chiamate di metodi, indicizzatori, operatori e costruttori di istanza usano la risoluzione dell'overload per determinare quale set candidato di membri della funzione richiamare. Questo processo è descritto in §12.6.4.

Dopo che un particolare membro della funzione è stato identificato in fase di associazione, possibilmente tramite la risoluzione dell'overload, il processo di runtime effettivo di richiamo del membro della funzione è descritto in §12.6.6.

Nota: la tabella seguente riepiloga l'elaborazione eseguita in costrutti che coinvolgono le sei categorie di membri della funzione che possono essere richiamati in modo esplicito. Nella tabella e, x, ye value indicano espressioni classificate come variabili o valori, T indica un'espressione classificata come tipo, F è il nome semplice di un metodo e P è il nome semplice di una proprietà.

Costruire Esempio Descrizione
Chiamata al metodo F(x, y) La risoluzione dell'overload si applica per selezionare il miglior metodo F nella classe o nella struttura contenitore. Il metodo viene richiamato con l'elenco di argomenti (x, y). Se il metodo non è static, l'espressione di istanza è this.
T.F(x, y) La risoluzione dell'overload viene applicata per selezionare il metodo migliore F nella classe o nella struttura T. Se il metodo non è static, si verifica un errore di data e ora di associazione. Il metodo viene richiamato con l'elenco di argomenti (x, y).
e.F(x, y) La risoluzione dell'overload viene applicata per selezionare il metodo migliore F nella classe, nella struttura o nell'interfaccia fornita dal tipo di e. Se il metodo è static, si verifica un errore di tempo di associazione. Il metodo viene richiamato con l'espressione di istanza e e l'elenco di argomenti (x, y).
Accesso alle proprietà P Viene richiamato il metodo di accesso 'get' della proprietà P nella classe o nella struttura contenitore. Se P è di sola scrittura, si verifica un errore in fase di compilazione. Se P non è static, l'espressione di istanza è this.
P = value La funzione di accesso set della proprietà P nella classe o nella struttura contenitore viene richiamata con l'elenco di argomenti (value). Se P è di sola lettura, si verifica un errore in fase di compilazione. Se P non è static, l'espressione di istanza è this.
T.P Viene richiamato l'accessor get della proprietà P nella classe o nello struct T. Si verifica un errore in fase di compilazione se P non è static o se P è di sola scrittura.
T.P = value La funzione di accesso set della proprietà P nella classe o nella struttura T viene richiamata con l'elenco di argomenti (value). Si verifica un errore in fase di compilazione se P non è static o se P è di sola lettura.
e.P La funzione di accesso get della proprietà P nella classe, nella struttura o nell'interfaccia specificata dal tipo di E viene invocata con l'espressione di istanza e. Si verifica un errore di binding se P è static o se P è ad accesso in scrittura esclusivo.
e.P = value La funzione di accesso set della proprietà P nella classe, nello struct o nell'interfaccia specificata dal tipo di E viene richiamata con l'espressione di istanza e e l'elenco di argomenti (value). Si verifica un errore di binding se P è static o se P è di sola lettura.
Accesso a eventi E += value Viene richiamato l'accessore add dell'evento E nella classe o struttura contenitore. Se E non è static, l'espressione di istanza è this.
E -= value Viene richiamato l'accessor remove dell'evento E nella classe o nella struttura contenitore. Se E non è static, l'espressione di istanza è this.
T.E += value Viene richiamata la funzione di accesso add dell'evento E nella classe o nello struct T. Se E non è static, si verifica un errore di binding-time.
T.E -= value Viene richiamato l'accessor remove dell'evento E nella classe o nello struct T. Se E non è static, si verifica un errore di binding-time.
e.E += value La funzione di accesso add dell'evento E nella classe, nello struct o nell'interfaccia specificata dal tipo di E viene richiamata con l'espressione di istanza e. Se E è static, si verifica un errore di tempo di associazione.
e.E -= value La funzione di accesso remove dell'evento E nella classe, nello struct o nell'interfaccia specificata dal tipo di E viene richiamata con l'espressione di istanza e. Se E è static, si verifica un errore di tempo di associazione.
Accesso all'indicizzatore e[x, y] La risoluzione dell'overload viene applicata per selezionare l'indicizzatore migliore nella classe, nello struct o nell'interfaccia specificata dal tipo di e. La funzione di accesso get dell'indicizzatore viene richiamata con l'espressione di istanza e e l'elenco di argomenti (x, y). Se l'indicizzatore è di sola scrittura, si verifica un errore di tempo di legame.
e[x, y] = value La risoluzione dell'overload viene applicata per selezionare l'indicizzatore migliore nella classe, nello struct o nell'interfaccia specificata dal tipo di e. La funzione di accesso set dell'indicizzatore viene richiamata con l'espressione di istanza e e l'elenco di argomenti (x, y, value). Se l'indicizzatore è di sola lettura, si verifica un errore di tempo di associazione.
Chiamata dell'operatore -x La risoluzione dell'overload viene applicata per selezionare l'operatore unario migliore nella classe o nello struct specificato dal tipo di x. L'operatore selezionato viene richiamato con l'elenco di argomenti (x).
x + y La risoluzione dell'overload viene applicata per selezionare l'operatore binario migliore nelle classi o negli struct forniti dai tipi di x e y. L'operatore selezionato viene richiamato con l'elenco di argomenti (x, y).
Chiamata al costruttore dell'istanza new T(x, y) La risoluzione dell'overload viene applicata per selezionare il costruttore di istanza migliore nella classe o nella struttura T. Il costruttore dell'istanza viene richiamato con l'elenco di argomenti (x, y).

nota finale

12.6.2 Elenchi di argomenti

12.6.2.1 Generale

Ogni membro di funzione e ogni invocazione di delegato includono un elenco di argomenti che fornisce i valori effettivi o i riferimenti a variabili corrispondenti ai parametri del membro di funzione. La sintassi per specificare l'elenco di argomenti di una chiamata a un membro di funzione dipende dalla categoria di membri della funzione:

  • Ad esempio, per i costruttori, i metodi, gli indicizzatori e i delegati, gli argomenti sono specificati come un elenco di argomenti , come descritto di seguito. Per gli indicizzatori, quando si richiama l'accessore 'set', l'elenco di argomenti include anche l'espressione specificata come operando destro dell'operatore di assegnazione.

    Nota: questo argomento aggiuntivo non viene usato per la risoluzione dell'overload, solo durante la chiamata della funzione di accesso set. nota finale

  • Per le proprietà, l'elenco di argomenti è vuoto quando si richiama l'accessor get ed è costituito dall'espressione specificata come operando destro dell'operatore di assegnazione quando si richiama l'accessor set.
  • Per gli eventi, l'elenco di argomenti è costituito dall'espressione specificata come operando destro dell'operatore += o -=.
  • Per gli operatori definiti dall'utente, l'elenco di argomenti è costituito dal singolo operando dell'operatore unario o dai due operandi dell'operatore binario.

Gli argomenti delle proprietà (§15.7) e gli eventi (§15.8) vengono sempre passati come parametri di valore (§15.6.2.2). Gli argomenti degli operatori definiti dall'utente (§15.10) vengono sempre passati come parametri di valore (§15.6.2.2) o parametri di input (§9.2.8). Gli argomenti degli indicizzatori (§15.9) vengono sempre passati come parametri di valore (§15.6.2.2), parametri di input (§9.2.8) o matrici di parametri (§15.6.2.4). I parametri di output e riferimento non sono supportati per queste categorie di membri della funzione.

Gli argomenti di un costruttore di istanza, un metodo, un indicizzatore o una chiamata di delegato vengono specificati come argument_list:

argument_list
    : argument (',' argument)*
    ;

argument
    : argument_name? argument_value
    ;

argument_name
    : identifier ':'
    ;

argument_value
    : expression
    | 'in' variable_reference
    | 'ref' variable_reference
    | 'out' variable_reference
    ;

Un argument_list è costituito da uno o più argomenti , separati da virgole. Ogni argomento è costituito da un nome_argomento facoltativo seguito da un valore_argomento. Un argomento con un argument_name viene definito argomento denominato, mentre un argomento senza un argument_name è un argomento posizionale .

Il argument_value può assumere una delle seguenti forme:

  • Un'espressione , che indica che l'argomento viene passato come parametro di valore o viene trasformato in un parametro di input e quindi passato come tale, come determinato da (§12.6.4.2 e descritto in §12.6.2.3.
  • La parola chiave in seguita da un variable_reference (§9.5), che indica che l'argomento viene passato come parametro di input (§15.6.2.3.2). Una variabile deve essere assegnata in modo definitivo (§9.4) prima che possa essere passata come parametro di input.
  • La parola chiave ref seguita da un variable_reference (§9.5), che indica che l'argomento viene passato come parametro di riferimento (§15.6.2.3.3). Una variabile deve essere assegnata in modo definitivo (§9.4) prima che possa essere passata come parametro di riferimento.
  • La parola chiave out seguita da un variable_reference (§9.5), che indica che l'argomento viene passato come parametro di output (§15.6.2.3.4). Una variabile viene considerata definitamente assegnata (§9.4) dopo una chiamata a un membro di funzione in cui la variabile viene passata come parametro di output.

Il formulario determina la modalità di passaggio dei parametri dell'argomento: rispettivamente valore, input, riferimentoo output. Tuttavia, come indicato in precedenza, un parametro con modalità di passaggio del valore potrebbe essere trasformato in un parametro con modalità di passaggio dell'input.

Il passaggio di un campo volatile (§15.5.4) come parametro di input, output o riferimento genera un avviso, poiché il campo potrebbe non essere considerato volatile dal metodo richiamato.

12.6.2.2 Parametri corrispondenti

Per ogni argomento in un elenco di argomenti deve essere presente un parametro corrispondente nel membro della funzione o nel delegato richiamato.

L'elenco di parametri usato nel codice seguente viene determinato nel modo seguente:

  • Per i metodi virtuali e gli indicizzatori definiti nelle classi, l'elenco dei parametri viene preso dalla prima dichiarazione o override del membro funzione trovato, partendo dal tipo statico del ricevitore e proseguendo la ricerca attraverso le sue classi di base.
  • Per i metodi parziali, si utilizza l'elenco dei parametri della dichiarazione di definizione del metodo parziale.
  • Per tutti gli altri membri e delegati di funzione è presente un solo elenco di parametri, che è quello usato.

La posizione di un argomento o di un parametro viene definita come il numero di argomenti o parametri che lo precede nell'elenco di argomenti o nell'elenco di parametri.

I parametri corrispondenti per gli argomenti membro della funzione vengono stabiliti come segue:

  • Argomenti nella argument_list di costruttori di istanze, metodi, indicizzatori e delegati:
    • Un argomento posizionale in cui un parametro si trova nella stessa posizione nell'elenco di parametri corrisponde a tale parametro, a meno che il parametro non sia una matrice di parametri e che il membro della funzione venga richiamato nella forma espansa.
    • Un argomento posizionale di un membro di funzione con una matrice di parametri richiamata nel formato espanso, che si verifica in corrispondenza o dopo la posizione della matrice di parametri nell'elenco dei parametri, corrisponde a un elemento nella matrice di parametri.
    • Un argomento denominato corrisponde al parametro con lo stesso nome nell'elenco dei parametri.
    • Per gli indicizzatori, quando si richiama la funzione di accesso set, l'espressione specificata come operando destro dell'operatore di assegnazione corrisponde al parametro implicito value della dichiarazione della funzione di accesso set.
  • Per le proprietà, quando si richiama l'accessor get non sono presenti argomenti. Quando si richiama l'accessor set, l'espressione indicata come operando destro dell'operatore di assegnazione corrisponde al parametro valore implicito della dichiarazione dell'accessor set.
  • Per gli operatori unari definiti dall'utente (incluse le conversioni), il singolo operando corrisponde al singolo parametro della dichiarazione dell'operatore.
  • Per gli operatori binari definiti dall'utente, l'operando sinistro corrisponde al primo parametro e l'operando destro corrisponde al secondo parametro della dichiarazione dell'operatore.
  • Un argomento senza nome non corrisponde a nessun parametro quando si trova dopo un argomento nominato fuori posizione o un argomento nominato che corrisponde a un array di parametri.

    Nota: impedisce che void M(bool a = true, bool b = true, bool c = true); venga richiamato da M(c: false, valueB);. Il primo argomento viene usato fuori posizione (l'argomento viene usato in prima posizione, ma il parametro denominato c è in terza posizione), quindi gli argomenti seguenti devono essere denominati. In altre parole, gli argomenti denominati non finali sono consentiti solo quando il nome e la posizione determinano la ricerca dello stesso parametro corrispondente. nota finale

12.6.2.3 Valutazione in fase di esecuzione delle liste di argomenti

Durante l'elaborazione in fase di esecuzione di una chiamata a un membro di funzione (§12.6.6), le espressioni o i riferimenti variabili di un elenco di argomenti vengono valutati in ordine, da sinistra a destra, come indicato di seguito:

  • Per un argomento valore, se la modalità di passaggio del parametro è valore

    • l'espressione dell'argomento viene valutata e viene eseguita una conversione implicita (§10.2) nel tipo di parametro corrispondente. Il valore risultante diventa il valore iniziale del parametro value nella chiamata del membro della funzione.

    • in caso contrario, la modalità di passaggio del parametro è input. Se l'argomento è un riferimento a variabile e esiste una conversione identity (§10.2.2) tra il tipo dell'argomento e il tipo del parametro, la posizione di archiviazione risultante diventa la posizione di archiviazione rappresentata dal parametro nella chiamata del membro della funzione. In caso contrario, viene creata una posizione di archiviazione con lo stesso tipo del parametro corrispondente. L'espressione dell'argomento viene valutata e viene eseguita una conversione implicita (§10.2) al tipo di parametro corrispondente. Il valore risultante viene archiviato all'interno di tale locazione di archiviazione. Tale posizione di archiviazione è rappresentata dal parametro di input nella chiamata del membro della funzione.

      Esempio: Per le seguenti dichiarazioni e chiamate di metodo:

      static void M1(in int p1) { ... }
      int i = 10;
      M1(i);         // i is passed as an input argument
      M1(i + 5);     // transformed to a temporary input argument
      

      Nella chiamata al metodo M1(i), i viene passato come argomento di input, perché è classificato come variabile e ha lo stesso tipo int del parametro di input. Nella chiamata al metodo M1(i + 5) viene creata una variabile int senza nome, inizializzata con il valore dell'argomento e quindi passata come argomento di input. Vedere §12.6.4.2 e §12.6.4.4.

      esempio finale

  • Per un input, un output o un argomento di riferimento, il riferimento alla variabile viene valutato e la posizione di memorizzazione risultante diventa quella rappresentata dal parametro nella chiamata del membro della funzione. Per un argomento di input o riferimento, la variabile verrà assegnata sicuramente al punto della chiamata al metodo. Se il riferimento alla variabile viene fornito come argomento di output o è un elemento di array di un reference_type, viene eseguito un controllo durante l'esecuzione per assicurarsi che il tipo di elemento dell'array sia identico al tipo del parametro. Se questo controllo ha esito negativo, viene generata una System.ArrayTypeMismatchException.

Nota: questo controllo di runtime è necessario a causa della covarianza della matrice (§17.6). nota finale

Esempio: nel codice seguente

class Test
{
    static void F(ref object x) {...}

    static void Main()
    {
        object[] a = new object[10];
        object[] b = new string[10];
        F(ref a[0]); // Ok
        F(ref b[1]); // ArrayTypeMismatchException
    }
}

La seconda chiamata di F provoca il lancio di una System.ArrayTypeMismatchException perché il tipo di elemento effettivo di b è string e non object.

esempio finale

I metodi, gli indicizzatori e i costruttori di istanza possono dichiarare il parametro più a destra come matrice di parametri (§15.6.2.4). Tali membri di funzione vengono richiamati nella loro forma normale o nella loro forma espansa a seconda di quale sia applicabile (§12.6.4.2):

  • Quando un membro di funzione con una matrice di parametri viene richiamato nel formato normale, l'argomento specificato per la matrice di parametri deve essere una singola espressione convertibile in modo implicito (§10.2) al tipo di matrice di parametri. In questo caso, la matrice di parametri agisce esattamente come un parametro di valore.
  • Quando un membro di funzione con una matrice di parametri viene richiamato nel formato espanso, la chiamata specifica zero o più argomenti posizionali per la matrice di parametri, dove ogni argomento è un'espressione convertibile in modo implicito (§10.2) al tipo di elemento della matrice di parametri. In questo caso, la chiamata crea un'istanza del tipo di matrice di parametri con una lunghezza corrispondente al numero di argomenti, inizializza gli elementi dell'istanza della matrice con i valori dell'argomento specificati e usa l'istanza della matrice appena creata come argomento effettivo.

Le espressioni di un elenco di argomenti vengono sempre valutate in ordine testuale.

esempio: quindi, l'esempio

class Test
{
    static void F(int x, int y = -1, int z = -2) =>
        Console.WriteLine($"x = {x}, y = {y}, z = {z}");

    static void Main()
    {
        int i = 0;
        F(i++, i++, i++);
        F(z: i++, x: i++);
    }
}

produce l'output

x = 0, y = 1, z = 2
x = 4, y = -1, z = 3

esempio finale

Quando un membro di funzione con una matrice di parametri viene richiamato nel formato espanso con almeno un argomento espanso, la chiamata viene elaborata come se un'espressione di creazione di matrice con un inizializzatore di matrice (§12.8.17.5) è stata inserita intorno agli argomenti espansi. Una matrice vuota viene passata quando non sono presenti argomenti per la matrice di parametri; non è specificato se il riferimento passato è a una matrice vuota appena allocata o esistente.

esempio: data la dichiarazione

void F(int x, int y, params object[] args);

le chiamate seguenti della forma espansa del metodo

F(10, 20, 30, 40);
F(10, 20, 1, "hello", 3.0);

corrispondono esattamente a

F(10, 20, new object[] { 30, 40 });
F(10, 20, new object[] { 1, "hello", 3.0 });

esempio finale

Quando gli argomenti vengono omessi da un membro della funzione con parametri facoltativi corrispondenti, gli argomenti predefiniti della dichiarazione membro della funzione vengono passati in modo implicito. Questo può comportare la creazione di una posizione di archiviazione, come descritto in precedenza.

Nota: Poiché questi elementi sono sempre costanti, la loro valutazione non influirà sulla valutazione degli argomenti rimanenti. nota finale

12.6.3 Inferenza dei tipi

12.6.3.1 Generale

Quando viene chiamato un metodo generico senza specificare argomenti di tipo, un processo di inferenza del tipo tenta di dedurre gli argomenti di tipo per la chiamata. La presenza di inferenza del tipo consente di usare una sintassi più comoda per chiamare un metodo generico e consente al programmatore di evitare di specificare informazioni sul tipo ridondanti.

esempio:

class Chooser
{
    static Random rand = new Random();

    public static T Choose<T>(T first, T second) =>
        rand.Next(2) == 0 ? first : second;
}

class A
{
    static void M()
    {
        int i = Chooser.Choose(5, 213); // Calls Choose<int>
        string s = Chooser.Choose("apple", "banana"); // Calls Choose<string>
    }
}

Tramite l'inferenza del tipo, gli argomenti di tipo int e string vengono determinati dagli argomenti del metodo.

esempio finale

L'inferenza del tipo si verifica come parte dell'elaborazione in fase di associazione di una chiamata al metodo (§12.8.10.2) e viene eseguita prima del passaggio di risoluzione dell'overload della chiamata. Quando un determinato gruppo di metodi viene specificato in una chiamata al metodo e non vengono specificati argomenti di tipo come parte della chiamata al metodo, l'inferenza del tipo viene applicata a ogni metodo generico nel gruppo di metodi. Se l'inferenza del tipo ha esito positivo, gli argomenti di tipo dedotti vengono usati per determinare i tipi di argomenti per la successiva risoluzione dell'overload. Se, nella risoluzione dell'overload, viene scelto un metodo generico da richiamare, gli argomenti di tipo dedotti vengono utilizzati come tipi di argomenti per l'invocazione. Se l'inferenza del tipo per un determinato metodo ha esito negativo, tale metodo non partecipa alla risoluzione dell'overload. L'insuccesso dell'inferenza del tipo, di per sé, non causa un errore di tempo di vincolo. Tuttavia, spesso genera un errore in fase di associazione quando la risoluzione dell'overload non riesce a trovare i metodi applicabili.

Se ogni argomento fornito non corrisponde esattamente a un parametro nel metodo (§12.6.2.2) oppure non esiste un parametro non facoltativo senza argomenti corrispondenti, l'inferenza ha immediatamente esito negativo. In caso contrario, si supponga che il metodo generico abbia la firma seguente:

Tₑ M<X₁...Xᵥ>(T₁ p₁ ... Tₓ pₓ)

Con una chiamata al metodo del form M(E₁ ...Eₓ) l'attività di inferenza del tipo consiste nel trovare argomenti di tipo univoci S₁...Sᵥ per ogni parametro di tipo X₁...Xᵥ in modo che la chiamata M<S₁...Sᵥ>(E₁...Eₓ) diventi valida.

Il processo di inferenza del tipo è descritto di seguito come algoritmo. Un compilatore conforme può essere implementato usando un approccio alternativo, purché raggiunga lo stesso risultato in tutti i casi.

Durante il processo di inferenza ogni parametro di tipo Xᵢ viene fisso a un particolare tipo Sᵢ o con un set associato di limiti di . Ogni limite è di tipo T. Inizialmente ogni variabile di tipo Xᵢ è non vincolata con un set vuoto di vincoli.

L'inferenza dei tipi viene eseguita in fasi. Ogni fase tenterà di dedurre argomenti di tipo per più variabili di tipo in base ai risultati della fase precedente. La prima fase esegue alcune inferenze sui limiti, mentre la seconda fase assegna le variabili di tipo in tipi specifici e deduce ulteriori limiti. La seconda fase può essere necessario ripetere un numero di volte.

Nota: l'inferenza del tipo viene usata anche in altri contesti, tra cui per la conversione di gruppi di metodi (§12.6.3.14) e trovare il tipo comune migliore di un set di espressioni (§12.6.3.15). nota finale

12.6.3.2 La prima fase

Per ogni argomento di metodo Eᵢ:

  • Se è una funzione anonima, viene un'inferenza esplicita del tipo di parametro (§12.6.3.8) daa
  • In caso contrario, se Eᵢ ha un tipo U e il parametro corrispondente è un parametro value (§15.6.2.2), viene un di inferenza con limite inferiore (§12.6.3.10) viene daUaTᵢ.
  • In caso contrario, se ha un tipo e il parametro corrispondente è un parametro di riferimento (§15.6.2.3.3) o parametro di output (§15.6.6. 2.3.4) viene quindi eseguita un'inferenza esatta (.12.6.3.9) daa.
  • In caso contrario, se Eᵢ ha un tipo U e il parametro corrispondente è un parametro di input (§15.6.2.3.2) e Eᵢ è un argomento di input, un 'inferenza esatta (§12.6.3.9) viene daUaTᵢ.
  • In caso contrario, se ha un tipo e il parametro corrispondente è un parametro di input (§15.6.2.3.2 ), viene daa.
  • In caso contrario, non viene eseguita alcuna inferenza per questo argomento.

12.6.3.3 La seconda fase

La seconda fase procede come segue:

  • Tutte le variabili di tipo senza prefisso che non dipendono da (§12.6.3.6) qualsiasi sono fisse (§12.6.3.12).
  • Se non esistono variabili di questo tipo, tutte le variabili di tipo non fissateXᵢ vengono fissate per le quali valgono tutte le seguenti condizioni:
    • Esiste almeno una variabile di tipo Xₑ da cui dipendeXᵢ
    • Xᵢ ha un set di limiti non vuoto
  • Se non esistono variabili di questo tipo e sono ancora presenti variabili di tipo non fissate, l'inferenza del tipo fallisce.
  • Altrimenti, se non esistono altre variabili di tipo non fissate, l'inferenza del tipo ha esito positivo.
  • In caso contrario, per tutti gli argomenti con il tipo di parametro corrispondente in cui i tipi di output (§12.6.3.5) contengono variabili di tipo non con prefisso , ma i tipi di input (§12.6.3.4 ) viene eseguita daa§12.6.3.7. La seconda fase viene quindi ripetuta.

12.6.3.4 Tipi di input

Se E è un gruppo di metodi o una funzione anonima tipizzata in modo implicito e T è un tipo delegato o un tipo di albero delle espressioni, tutti i tipi di parametro di T sono tipi di input diEcon tipoT.

12.6.3.5 Tipi di output

Se E è un gruppo di metodi o una funzione anonima e T è un tipo delegato o un tipo di albero delle espressioni, il tipo restituito di T è un tipo di output diEcon tipoT.

12.6.3.6 Dipendenza

Una variabile di tipo con prefisso dipende direttamente da una variabile di tipo con prefisso se per alcuni argomenti con tipo si verifica in un tipo di input di con tipo e si verifica in un tipo di output di con tipo .

Xₑ dipende daXᵢ se Xₑdipende direttamente dallaXᵢ o se Xᵢdipende direttamente daXᵥ e Xᵥdipende daXₑ. Pertanto "dipende da" è la chiusura transitiva ma non riflessiva di "dipende direttamente da".

12.6.3.7 Inferenze del tipo di output

Un 'inferenza del tipo di output viene eseguita da un'espressione Ea un tipo T nel modo seguente:

  • Se è una funzione anonima con tipo restituito dedotto (§12.6.3.13) e è un tipo delegato o albero delle espressioni con tipo restituito , viene un'inferenza (§12.6.3.10) daa.
  • In caso contrario, se è un gruppo di metodi e è un tipo delegato o un tipo di albero delle espressioni con tipi di parametro e il tipo restituito e la risoluzione dell'overload di con i tipi restituisce un singolo metodo con tipo restituito , viene eseguita una di inferenza con limite inferiore daa.
  • In caso contrario, se E è un'espressione con tipo U, viene fatta un'inferenza con limite inferioredaUaT.
  • In caso contrario, non vengono effettuate inferenze.

12.6.3.8 Inferenze esplicite del tipo di parametro

L''inferenza esplicita del tipo di parametro è tratta da un'espressione Ea un tipo T nel seguente modo:

  • Se E è una funzione anonima tipizzata in modo esplicito con tipi di parametro U₁...Uᵥ e T è un tipo delegato o un tipo di albero delle espressioni con tipi di parametro V₁...Vᵥ quindi per ogni Uᵢ un di inferenza esatta (§12.6.3.9) viene daUᵢa il Vᵢcorrispondente.

12.6.3.9 Inferenze esatte

Una di inferenza esatta da un tipo Ua viene eseguita una V di tipo come indicato di seguito:

  • Se V è uno dei Xᵢ senza prefisso, U viene aggiunto all'insieme di limiti esatti per Xᵢ.
  • In caso contrario, i set di V₁...Vₑ e U₁...Uₑ vengono determinati controllando se si applica uno dei casi seguenti:
    • V è un tipo di matrice V₁[...] e U è un tipo di matrice U₁[...] dello stesso rango
    • V è il tipo V₁? e U è il tipo U₁
    • V è un tipo costruito C<V₁...Vₑ> e U è un tipo costruito C<U₁...Uₑ>
      Se uno di questi casi si applica, viene eseguita una di inferenza esatta da ogni all'corrispondente.
  • In caso contrario, non vengono effettuate inferenze.

12.6.3.10 Inferenze con limiti inferiori

Un'inferenza limite inferiore da un tipo Ua viene eseguita una V di tipo come indicato di seguito:

  • Se V è uno dei non fissatiXᵢ, U viene aggiunto all'insieme di limiti inferiori per Xᵢ.
  • In caso contrario, se V è il tipo V₁? e U è il tipo U₁?, viene eseguita un'inferenza con limite inferiore da U₁ a V₁.
  • In caso contrario, i set di U₁...Uₑ e V₁...Vₑ vengono determinati controllando se si applica uno dei casi seguenti:
    • V è un tipo di matrice V₁[...]e U è un tipo di matrice U₁[...]dello stesso rango
    • V è una delle IEnumerable<V₁>, ICollection<V₁>, IReadOnlyList<V₁>>, IReadOnlyCollection<V₁> o IList<V₁> e U è un tipo di matrice unidimensionale U₁[]
    • V è un classcostruito, struct, interface o delegate di tipo C<V₁...Vₑ> e esiste un tipo univoco C<U₁...Uₑ> tale che U (o, se U è un tipo parameter, la relativa classe base effettiva o qualsiasi membro del set di interfacce effettivo) sia identica, inherits da (direttamente o indirettamente), o implementi (direttamente o indirettamente) C<U₁...Uₑ>.
    • La restrizione "univocità" indica che nel caso dell'interfaccia C<T>{} class U: C<X>, C<Y>{}, non viene eseguita alcuna inferenza quando si deduce da U a C<T> perché U₁ potrebbe essere X o Y.
      Se uno di questi casi si applica, viene eseguita un'inferenza da ogni Uᵢ al Vᵢ corrispondente come indicato di seguito:
    • Se Uᵢ non è noto per essere un tipo di riferimento, allora viene eseguita un'inferenza esatta
    • In caso contrario, se U è un tipo di matrice, viene eseguita un'inferenza di associazione inferiore
    • In caso contrario, se V è C<V₁...Vₑ> l'inferenza dipende dal parametro di tipo i-th di C:
      • Se è covariante, viene eseguita un'inferenza del limite inferiore .
      • Se è controvariante, viene eseguita un'inferenza limite superiore.
      • Se è invariante, viene eseguita un'inferenza esatta.
  • In caso contrario, non vengono effettuate inferenze.

Inferenze con limite superiore 12.6.3.11

L'inferenza del limite superiore da un tipo UV viene eseguita come segue:

  • Se è uno dei non fissati , viene aggiunto al set di limiti superiori per .
  • In caso contrario, i set di V₁...Vₑ e U₁...Uₑ vengono determinati controllando se si applica uno dei casi seguenti:
    • U è un tipo di matrice U₁[...]e V è un tipo di matrice V₁[...]dello stesso rango
    • U è una delle IEnumerable<Uₑ>, ICollection<Uₑ>, IReadOnlyList<Uₑ>, IReadOnlyCollection<Uₑ> o IList<Uₑ> e V è un tipo di matrice unidimensionale Vₑ[]
    • U è il tipo U1? e V è il tipo V1?
    • U è una classe, una struttura, un'interfaccia o un tipo delegato C<U₁...Uₑ> e V è un tipo class, struct, interface o delegate che è identical verso, inherits da (in modo diretto o indiretto), o implementa (in modo diretto o indiretto) un tipo unico C<V₁...Vₑ>
    • (La restrizione "univocità" indica che dato un'interfaccia C<T>{} class V<Z>: C<X<Z>>, C<Y<Z>>{}, non viene eseguita alcuna inferenza quando si deduce da C<U₁> a V<Q>. Le inferenze non vengono eseguite da U₁ a X<Q> o Y<Q>.
      Se uno di questi casi si applica, viene eseguita un'inferenza da ogni Uᵢ al Vᵢ corrispondente come indicato di seguito:
    • Se Uᵢ non è noto per essere un tipo di riferimento, allora viene eseguita un'inferenza esatta
    • In caso contrario, se V è un tipo di matrice, viene eseguita un'inferenza del limite superiore .
    • In caso contrario, se U è C<U₁...Uₑ> l'inferenza dipende dal parametro di tipo i-th di C:
      • Se è covariante, viene eseguita un'inferenza di limite superiore .
      • Se è controvariante, viene eseguita un'inferenza del limite inferiore .
      • Se è invariante, viene eseguita un'inferenza esatta.
  • In caso contrario, non vengono effettuate inferenze.

12.6.3.12 Correzione

Un variabile di tipo non fissato Xᵢ con un set di limiti è fissato come indicato di seguito:

  • Il set di tipi candidati Uₑ è inizialmente il set di tutti i tipi nel set di limiti per Xᵢ.
  • Ogni limite di Xᵢ viene esaminato: per ogni limite esatto U di Xᵢ, tutti i tipi Uₑ che non sono identici a U vengono rimossi dal set dei candidati. Per ogni U con limite inferiore di Xᵢ tutti i tipi Uₑ a cui non è una conversione implicita da U vengono rimossi dal set di candidati. Per ogni U con limite superiore di Xᵢ tutti i tipi Uₑ da cui è presente non una conversione implicita in U vengono rimossi dal set candidato.
  • Se tra i tipi candidati rimanenti Uₑ esiste un tipo univoco V a cui esiste una conversione implicita da tutti gli altri tipi candidati, allora Xᵢ viene fissato a V.
  • In caso contrario, l'inferenza del tipo fallisce.

12.6.3.13 Tipo restituito dedotto

Il tipo di ritorno dedotto di una funzione anonima F viene utilizzato nell'inferenza del tipo e nella risoluzione del sovraccarico. Il tipo restituito dedotto può essere determinato solo per una funzione anonima in cui tutti i tipi di parametro sono noti, poiché vengono specificati esplicitamente, forniti tramite una conversione di funzione anonima o dedotti durante l'inferenza del tipo in una chiamata a un metodo generico circostante.

Il tipo restituito dedotto viene determinato come segue:

  • Se il corpo di F è un'espressione che ha un tipo, allora il tipo di ritorno effettivo dedotto di F è il tipo di quell'espressione.
  • Se il corpo di F è un blocco e il set di espressioni nelle istruzioni return del blocco ha un miglior tipo comune T (§12.6.3.15), il tipo restituito effettivo di F dedotto è T.
  • In caso contrario, non è possibile dedurre un tipo restituito effettivo per F.

Il tipo di ritorno dedotto viene determinato nel modo seguente:

  • Se F è asincrono e il corpo di F è un'espressione classificata come nulla (§12.2) o un blocco in cui nessuna istruzione return dispone di espressioni, il tipo restituito dedotto è «TaskType» (§15.15.1).
  • Se F è asincrono e ha un tipo di ritorno dedotto T, allora il tipo di ritorno dedotto è «TaskType»<T>»(§15.15.1).
  • Se F non è asincrono e ha un tipo restituito effettivo dedotto T, il tipo restituito dedotto è T.
  • In caso contrario, non è possibile dedurre un tipo restituito per F.

esempio: come esempio di inferenza dei tipi che include funzioni anonime, considera il metodo di estensione Select dichiarato nella classe System.Linq.Enumerable:

namespace System.Linq
{
    public static class Enumerable
    {
        public static IEnumerable<TResult> Select<TSource,TResult>(
            this IEnumerable<TSource> source,
            Func<TSource,TResult> selector)
        {
            foreach (TSource element in source)
            {
                yield return selector(element);
            }
        }
   }
}

Supponendo che lo spazio dei nomi System.Linq sia stato importato con una direttiva using namespace e data una classe Customer con una proprietà Name di tipo string, è possibile usare il metodo Select per selezionare i nomi di un elenco di clienti:

List<Customer> customers = GetCustomerList();
IEnumerable<string> names = customers.Select(c => c.Name);

La chiamata al metodo di estensione (§12.8.10.3) di Select viene trasformata riscrivendola in una chiamata a un metodo statico.

IEnumerable<string> names = Enumerable.Select(customers, c => c.Name);

Poiché gli argomenti di tipo non sono stati specificati in modo esplicito, l'inferenza del tipo viene usata per dedurre gli argomenti di tipo. In primo luogo, l'argomento relativo ai clienti è correlato al parametro di origine, deducendo che TSource sia Customer. Usando quindi il processo di inferenza del tipo di funzione anonima descritto in precedenza, c viene assegnato il tipo Customere l'espressione c.Name è correlata al tipo restituito del parametro del selettore, deducendo TResult da string. Pertanto, la chiamata è equivalente a

Sequence.Select<Customer,string>(customers, (Customer c) => c.Name)

e il risultato è di tipo IEnumerable<string>.

Nell'esempio seguente viene illustrato il modo in cui l'inferenza del tipo di funzione anonima consente alle informazioni di tipo di "fluire" tra gli argomenti in una chiamata di un metodo generico. Dato il metodo e la chiamata seguenti:

class A
{
    static Z F<X,Y,Z>(X value, Func<X,Y> f1, Func<Y,Z> f2)
    {
        return f2(f1(value));
    }

    static void M()
    {
        double hours = F("1:15:30", s => TimeSpan.Parse(s), t => t.TotalHours);
    }
}

L'inferenza del tipo per la chiamata procede come indicato di seguito: In primo luogo, l'argomento "1:15:30" è correlato al parametro di valore, deducendo che X sia di tipo stringa. Il parametro della prima funzione anonima, s, viene quindi assegnato il tipo che è stato dedotto, string, e l'espressione TimeSpan.Parse(s) è correlata al tipo di ritorno di f1, permettendo di dedurre che Y è System.TimeSpan. Infine, al parametro della seconda funzione anonima, t, viene assegnato il tipo dedotto System.TimeSpane l'espressione t.TotalHours è correlata al tipo restituito di f2, deducendo Z da double. Di conseguenza, il risultato della chiamata è di tipo double.

esempio finale

12.6.3.14 Inferenza dei tipi per la conversione dei gruppi di metodi

Analogamente alle chiamate di metodi generici, l'inferenza dei tipi deve essere applicata anche quando un gruppo di metodi M contenente un metodo generico viene convertito in un determinato tipo delegato D (§10.8). Dato un metodo

Tₑ M<X₁...Xᵥ>(T₁ x₁ ... Tₑ xₑ)

e il gruppo di metodi M che viene assegnato al tipo delegato D l'attività di inferenza del tipo consiste nel trovare argomenti di tipo S₁...Sᵥ, in modo tale che l'espressione:

M<S₁...Sᵥ>

diventa compatibile (§20.2) con D.

A differenza dell'algoritmo di inferenza del tipo per le chiamate al metodo generico, in questo caso sono disponibili solo tipi di argomento , nessuna espressione di argomento . In particolare, non esistono funzioni anonime e quindi non è necessario eseguire più fasi di inferenza.

Tutte le vengono invece considerate senza prefisso e viene eseguita un di inferenza limite inferiore da ogni tipo di argomento di a il tipo di parametro corrispondente di . Nel caso in cui per uno dei Xᵢ non siano stati trovati limiti, l'inferenza del tipo fallisce. In caso contrario, tutte le Xᵢ vengono fisse a Sᵢcorrispondenti, che sono il risultato dell'inferenza del tipo.

12.6.3.15 Ricerca del tipo comune migliore di un set di espressioni

In alcuni casi, è necessario dedurre un tipo comune per un set di espressioni. In particolare, i tipi di elemento di matrici tipizzate in modo implicito e i tipi restituiti di funzioni anonime con blocco corpi vengono trovati in questo modo.

Il tipo comune migliore per un set di espressioni E₁...Eᵥ viene determinato come segue:

  • Viene introdotta una nuova variabile di tipo non fissata .
  • Per ogni espressione Ei viene eseguita un'inferenza del tipo di output (§12.6.3.7) da esso a X.
  • X è fisso (§12.6.3.12), se possibile, e il tipo risultante è il tipo comune migliore.
  • L'inferenza fallisce altrimenti.

Nota: intuitivamente, questa inferenza equivale a chiamare un metodo void M<X>(X x₁ ... X xᵥ) con l'Eᵢ come argomenti e inferire X. nota finale

12.6.4 Risoluzione dell'overload

12.6.4.1 Generale

La risoluzione dell'overload è un meccanismo di binding-time per la selezione della migliore funzione da richiamare in base a un elenco di argomenti e a un set di membri di funzione candidati. La risoluzione dell'overload seleziona il membro della funzione da richiamare nei contesti distinti seguenti all'interno di C#:

  • Chiamata di un metodo indicato in un invocation_expression (§12.8.10).
  • Chiamata di un costruttore di istanza specificato in un object_creation_expression (§12.8.17.2).
  • Chiamata di una funzione di accesso dell'indicizzatore tramite un element_access (§12.8.12).
  • Chiamata di un operatore predefinito o definito dall'utente a cui si fa riferimento in un'espressione (§12.4.4 e §12.4.5).

Ognuno di questi contesti definisce il set di membri della funzione candidata e l'elenco di argomenti in modo univoco. Ad esempio, il set di candidati per una chiamata al metodo non include metodi contrassegnati come override (§12.5) e i metodi in una classe base non sono candidati se un metodo in una classe derivata è applicabile (§12.8.10.2).

Dopo aver identificato i membri della funzione candidata e l'elenco di argomenti, la selezione del membro della funzione migliore è la stessa in tutti i casi:

  • In primo luogo, il set di membri candidati della funzione viene ridotto a quei membri della funzione che sono applicabili rispetto all'elenco di argomenti dato (§12.6.4.2). Se questo set ridotto è vuoto, si verifica un errore in fase di compilazione.
  • Viene quindi individuato il membro di funzione migliore del set di membri della funzione candidata applicabili. Se il set contiene un solo membro della funzione, tale membro della funzione è il membro della funzione migliore. In caso contrario, il membro di funzione migliore è quello che è meglio di tutti gli altri membri di funzione rispetto all'elenco di argomenti dato, purché ciascun membro di funzione venga confrontato con tutti gli altri membri di funzione utilizzando le regole in §12.6.4.3. Se non esiste esattamente un membro di funzione migliore di tutti gli altri membri della funzione, la chiamata del membro della funzione è ambigua e si verifica un errore di binding.

Le sottoclause seguenti definiscono i significati esatti dei termini membro della funzione applicabile e membro di funzione migliore.

12.6.4.2 Membro della funzione applicabile

Un membro della funzione è detto essere un membro della funzione applicabile rispetto a un elenco di argomenti A quando sono soddisfatte tutte le condizioni seguenti:

  • Ogni argomento in A corrisponde a un parametro nella dichiarazione del membro della funzione come descritto in §12.6.2.2, al massimo un argomento corrisponde a ogni parametro e qualsiasi parametro a cui nessun argomento corrisponde è un parametro facoltativo.
  • Per ogni argomento in A, la modalità di passaggio dei parametri dell'argomento è identica alla modalità di passaggio dei parametri del parametro corrispondente e
    • per un parametro di valore o un array di parametri, esiste una conversione implicita (§10.2) dall'espressione dell'argomento al tipo del parametro corrispondente oppure
    • per un riferimento o un parametro di output, è presente una conversione identity tra il tipo dell'espressione di argomento (se presente) e il tipo del parametro corrispondente oppure
    • per un parametro di input, quando l'argomento corrispondente ha il modificatore in, esiste una conversione di identità tra il tipo dell'espressione dell'argomento (se presente) e il tipo del parametro corrispondente, oppure
    • per un parametro di input quando l'argomento corrispondente omette il modificatore in, esiste una conversione implicita (§10.2) dall'espressione dell'argomento al tipo del parametro corrispondente.

Per un membro della funzione che include una matrice di parametri, se il membro della funzione è applicabile dalle regole precedenti, si dice che sia applicabile nel formato normale . Se un membro della funzione che include una matrice di parametri non è applicabile nel formato normale, il membro della funzione potrebbe essere invece applicabile nel formato espanso :

  • La forma espansa viene costruita sostituendo la matrice di parametri nella dichiarazione membro della funzione con zero o più parametri di valore del tipo di elemento della matrice di parametri in modo che il numero di argomenti nell'elenco di argomenti A corrisponda al numero totale di parametri. Se A ha meno argomenti rispetto al numero di parametri fissi nella dichiarazione del membro della funzione, la forma espansa del membro della funzione non può essere costruita e pertanto non è applicabile.
  • In caso contrario, il modulo espanso è applicabile se per ogni argomento in A, è vero uno dei seguenti:
    • la modalità di passaggio dei parametri dell'argomento è identica alla modalità di passaggio dei parametri del parametro corrispondente e
      • per un parametro di valore costante o un parametro di valore creato dall'espansione, esiste una conversione implicita (§10.2) dall'espressione dell'argomento al tipo del parametro corrispondente.
      • per un parametro per riferimento, il tipo dell'espressione dell'argomento è identico al tipo del parametro corrispondente.
    • la modalità di passaggio di parametri dell'argomento è valore, mentre quella del parametro corrispondente è di input, ed esiste una conversione implicita (§10.2) dall'espressione dell'argomento al tipo del parametro corrispondente.

Quando la conversione implicita dal tipo di argomento al tipo di parametro di un parametro di input è una conversione implicita dinamica (§10.2.10), i risultati non sono definiti.

Esempio: Per le seguenti dichiarazioni e chiamate di metodo:

public static void M1(int p1) { ... }
public static void M1(in int p1) { ... }
public static void M2(in int p1) { ... }
public static void Test()
{
    int i = 10; uint ui = 34U;

    M1(in i);   // M1(in int) is applicable
    M1(in ui);  // no exact type match, so M1(in int) is not applicable
    M1(i);      // M1(int) and M1(in int) are applicable
    M1(i + 5);  // M1(int) and M1(in int) are applicable
    M1(100u);   // no implicit conversion exists, so M1(int) is not applicable

    M2(in i);   // M2(in int) is applicable
    M2(i);      // M2(in int) is applicable
    M2(i + 5);  // M2(in int) is applicable
}

esempio finale

  • Un metodo statico è applicabile solo se il gruppo di metodi restituisce un simple_name o un member_access tramite un tipo.
  • Un metodo di istanza è applicabile solo se il gruppo di metodi risulta da un simple_name, un member_access tramite una variabile o un valore, o un base_access.
    • Se il gruppo di metodi risulta da un simple_name, un metodo di istanza è applicabile solo se this accesso è consentito §12.8.14.
  • Quando il gruppo di metodi deriva da un member_access che può essere tramite un'istanza o un tipo come descritto in §12.8.7.2, sono applicabili sia metodi di istanza che statici.
  • Un metodo generico i cui argomenti di tipo (specificati o dedotti in modo esplicito) non soddisfano tutti i vincoli non è applicabile.
  • Nel contesto della conversione di un gruppo di metodi, esiste una conversione di identità (§10.2.2) o una conversione di riferimento implicita (§10.2.8) dal tipo restituito del metodo al tipo restituito del delegato. In caso contrario, il metodo candidato non è applicabile.

12.6.4.3 Membro di funzione migliore

Ai fini della determinazione del membro di funzione migliore, viene costruito un elenco semplificato di argomenti A che contiene solo le espressioni degli argomenti nell'ordine in cui appaiono nell'elenco di argomenti originale, escludendo qualsiasi argomento out o ref.

Gli elenchi di parametri per ognuno dei membri della funzione candidata vengono costruiti nel modo seguente:

  • Il modulo espanso viene utilizzato se il membro della funzione è applicabile solo nel modulo espanso.
  • I parametri facoltativi senza argomenti corrispondenti vengono rimossi dall'elenco di parametri
  • I parametri di riferimento e di output vengono rimossi dall'elenco di parametri
  • I parametri vengono riordinati in modo che si verifichino nella stessa posizione dell'argomento corrispondente nell'elenco di argomenti.

Dato un elenco di argomenti A con un set di espressioni di argomento {E₁, E₂, ..., Eᵥ} e due membri di funzione applicabili Mᵥ e Mₓ con tipi di parametro {P₁, P₂, ..., Pᵥ} e {Q₁, Q₂, ..., Qᵥ}, Mᵥ è definito come membro di funzione migliore rispetto a Mₓ se

  • per ogni argomento, la conversione implicita da Eᵥ a Qᵥ non è migliore della conversione implicita da Eᵥ a Pᵥe
  • per almeno un argomento, la conversione da Eᵥ a Pᵥ è migliore rispetto alla conversione da Eᵥ a Qᵥ.

Nel caso in cui le sequenze di tipi di parametro {P₁, P₂, ..., Pᵥ} e {Q₁, Q₂, ..., Qᵥ} siano equivalenti (ad esempio, ogni Pᵢ ha una conversione di identità nell'Qᵢcorrispondente ), vengono applicate le regole di interruzione di associazione seguenti, per determinare il membro della funzione migliore.

  • Se Mᵢ è un metodo non generico e Mₑ è un metodo generico, Mᵢ è migliore di Mₑ.
  • In caso contrario, se Mᵢ è applicabile nella forma normale e Mₑ ha una matrice params ed è applicabile solo nel formato espanso, Mᵢ è migliore di Mₑ.
  • In caso contrario, se entrambi i metodi hanno matrici di parametri e sono applicabili solo nei moduli espansi e se la matrice params di Mᵢ ha meno elementi rispetto alla matrice param di Mₑ, Mᵢ è migliore di Mₑ.
  • In caso contrario, se Mᵥ ha tipi di parametri più specifici di Mₓ, Mᵥ è migliore di Mₓ. Lasciare che {R1, R2, ..., Rn} e {S1, S2, ..., Sn} rappresentino i tipi di parametri non istanziati e non espansi di Mᵥ e Mₓ. i tipi di parametro di Mᵥsono più specifici di Mₓse, per ogni parametro, Rx non è meno specifico di Sxe, per almeno un parametro, Rx è più specifico di Sx:
    • Un parametro di tipo è meno specifico di un parametro non di tipo.
    • In modo ricorsivo, un tipo costruito è più specifico di un altro tipo costruito (con lo stesso numero di argomenti di tipo) se almeno un argomento di tipo è più specifico e nessun argomento di tipo è meno specifico dell'argomento di tipo corrispondente nell'altro.
    • Un tipo di matrice è più specifico di un altro tipo di matrice (con lo stesso numero di dimensioni) se il tipo di elemento del primo è più specifico del tipo di elemento del secondo.
  • In caso contrario, se un membro è un operatore non liftato e l'altro è un operatore liftato, quello non liftato è migliore.
  • Se nessun membro della funzione è stato trovato migliore e tutti i parametri di Mᵥ hanno un argomento corrispondente, mentre gli argomenti predefiniti devono essere sostituiti per almeno un parametro facoltativo in Mₓ, Mᵥ è migliore di Mₓ.
  • Se per almeno un parametro Mᵥ usa la scelta migliore per il passaggio di parametri (§12.6.4.4) rispetto al parametro corrispondente in Mₓ e nessuno dei parametri in Mₓ usa la scelta migliore di passaggio dei parametri rispetto a Mᵥ, allora Mᵥ è migliore di Mₓ.
  • In caso contrario, nessun membro della funzione è migliore.

12.6.4.4.4 Modalità di passaggio dei parametri migliore

È consentito disporre di parametri corrispondenti in due metodi di overload diversi solo in base alla modalità di passaggio dei parametri, purché uno dei due parametri abbia la modalità di passaggio del valore, come indicato di seguito:

public static void M1(int p1) { ... }
public static void M1(in int p1) { ... }

Dato int i = 10;, in base a §12.6.4.2, le chiamate M1(i) e M1(i + 5) fanno sì che entrambi gli overload siano applicabili. In questi casi, il metodo con la modalità di passaggio dei parametri per valore è la migliore scelta della modalità di passaggio dei parametri .

Nota: nessuna scelta di questo tipo è necessaria per argomenti di modalità di input, output o passaggio di riferimenti, in quanto tali argomenti corrispondono solo alla stessa modalità di passaggio dello stesso parametro. nota finale

12.6.4.5 Conversione migliore da espressione

Data una conversione implicita C₁ che esegue la conversione da un'espressione E a un tipo T₁e una conversione implicita C₂ che converte da un'espressione E a un tipo T₂, C₁ è una conversione migliore rispetto a C₂ se si verifica una delle seguenti condizioni:

  • E corrisponde esattamente T₁ e E non corrisponde esattamente T₂ (§12.6.4.6)
  • E corrisponde esattamente sia a T₁ che a T₂, oppure a nessuno dei due, e T₁ è una destinazione di conversione migliore di T₂ (§12.6.4.7)
  • E è un gruppo di metodi (§12.2), T₁ è compatibile (§20.4) con il metodo migliore del gruppo di metodi per la conversione C₁e T₂ non è compatibile con il singolo metodo del gruppo di metodi per la conversione C₂

12.6.4.6 Corrispondenza esatta dell'espressione

Dato un'espressione E e un tipo T, Ecorrisponde esattamenteT se uno dei seguenti casi:

  • E ha un tipo Se esiste una conversione di identità da S a T
  • E è una funzione anonima, T è un tipo delegato D o un tipo di albero delle espressioni Expression<D> e vale una delle seguenti condizioni:
    • Esiste un tipo restituito dedotto X per E nel contesto dell'elenco di parametri di D (§12.6.3.12) ed esiste una conversione di identità da X al tipo restituito di D
    • E è una lambda async senza valore di ritorno e D ha un tipo di ritorno che è un «TaskType» non generico
    • E non è asincrono e D ha un tipo di ritorno Y oppure E è asincrono e D ha un tipo di ritorno «TaskType»<Y>(§15.15.1), e si verifica uno dei seguenti casi:
      • Il corpo di E è un'espressione che corrisponde esattamente a Y
      • Il corpo di E è un blocco in cui ogni istruzione return restituisce un'espressione che corrisponde esattamente a Y

12.6.4.7 Destinazione di conversione migliore

Dati due tipi T₁ e T₂, T₁ è una destinazione di conversione migliore rispetto a T₂ se si verifica una delle seguenti condizioni:

  • Esiste una conversione implicita da T₁ a T₂ e non esiste alcuna conversione implicita da T₂ a T₁
  • T₁ è «TaskType»<S₁>(§15.15.1), T₂ è «TaskType»<S₂>e S₁ è una destinazione di conversione migliore di S₂
  • T₁ è «TaskType»<S₁>(§15.15.1), T₂ è «TaskType»<S₂>e T₁ è più specializzato di T₂
  • T₁ è S₁ o S₁? in cui S₁ è un tipo integrale con segno e T₂ è S₂ o S₂? dove S₂ è un tipo integrale senza segno. Specificamente:
    • S₁ è sbyte e S₂ è byte, ushort, uinto ulong
    • S₁ è short e S₂ è ushort, uinto ulong
    • S₁ è int e S₂ è uinto ulong
    • S₁ è long e S₂ è ulong

12.6.4.8 Sovraccarico nelle classi generiche

Nota: mentre le firme dichiarate devono essere univoche (§8.6), è possibile che la sostituzione degli argomenti di tipo restituisca firme identiche. In una situazione di questo tipo, la risoluzione dell'overload sceglierà il più specifico (§12.6.4.3) delle firme originali (prima della sostituzione degli argomenti di tipo), se esiste; in caso contrario, segnalerà un errore. nota finale

Esempio: gli esempi seguenti mostrano sovraccarichi validi e non validi in base a questa regola:

public interface I1<T> { ... }
public interface I2<T> { ... }

public abstract class G1<U>
{
    public abstract int F1(U u);           // Overload resolution for G<int>.F1
    public abstract int F1(int i);         // will pick non-generic

    public abstract void F2(I1<U> a);      // Valid overload
    public abstract void F2(I2<U> a);
}

abstract class G2<U,V>
{
    public abstract void F3(U u, V v);     // Valid, but overload resolution for
    public abstract void F3(V v, U u);     // G2<int,int>.F3 will fail

    public abstract void F4(U u, I1<V> v); // Valid, but overload resolution for
    public abstract void F4(I1<V> v, U u); // G2<I1<int>,int>.F4 will fail

    public abstract void F5(U u1, I1<V> v2);   // Valid overload
    public abstract void F5(V v1, U u2);

    public abstract void F6(ref U u);      // Valid overload
    public abstract void F6(out V v);
}

esempio finale

12.6.5 Controllo in fase di compilazione della chiamata dinamica dei membri

Anche se la risoluzione dell'overload di un'operazione con associazione dinamica avviene in fase di esecuzione, talvolta è possibile in fase di compilazione conoscere l'elenco dei membri di funzione da cui verrà scelto un sovraccarico:

  • Per una chiamata di delegato (§12.8.10.4), l'elenco è un singolo membro della funzione con lo stesso elenco di parametri del delegate_type della chiamata
  • Per una chiamata al metodo (§12.8.10.2) su un tipo o su un valore il cui tipo statico non è dinamico, il set di metodi accessibili nel gruppo di metodi è noto in fase di compilazione.
  • Per un'espressione di creazione di oggetti (§12.8.17.2) il set di costruttori accessibili nel tipo è noto in fase di compilazione.
  • Per l'accesso di un indicizzatore (§12.8.12.3), il gruppo di indicizzatori accessibili nel ricevitore è noto al momento della compilazione.

In questi casi viene eseguito un controllo limitato in fase di compilazione su ogni membro nel set noto di membri di funzione, per essere certi che non venga mai richiamato in fase di esecuzione. Per ogni membro della funzione F vengono costruiti un parametro modificato e un elenco di argomenti:

  • In primo luogo, se F è un metodo generico e sono stati forniti argomenti di tipo, tali argomenti vengono sostituiti per i parametri di tipo nell'elenco dei parametri. Tuttavia, se gli argomenti di tipo non sono stati forniti, non viene eseguita alcuna sostituzione di questo tipo.
  • Quindi, qualsiasi parametro il cui tipo è aperto (cioè contiene un parametro di tipo; vedere §8.4.3) viene omesso, insieme ai parametri corrispondenti.

Affinché F superi il controllo, devono valere tutte le seguenti condizioni:

  • L'elenco di parametri modificato per F è applicabile all'elenco di argomenti modificato in termini di §12.6.4.2.
  • Tutti i tipi costruiti nell'elenco di parametri modificati soddisfano i relativi vincoli (§8.4.5).
  • Se i parametri di tipo di F sono stati sostituiti nel passaggio precedente, i relativi vincoli vengono soddisfatti.
  • Se F è un metodo statico, il gruppo di metodi non avrà generato un member_access il cui ricevitore è noto in fase di compilazione come variabile o valore.
  • Se F è un metodo di istanza, il gruppo di metodi non deve derivare da un accesso_membro il cui ricevitore è noto in fase di compilazione come tipo.

Se nessun candidato supera questo test, si verifica un errore in fase di compilazione.

12.6.6 Chiamata del membro della funzione

12.6.6.1 Generale

Questa sottochiave descrive il processo che viene eseguito in fase di esecuzione per richiamare un determinato membro della funzione. Si presuppone che un processo in fase di associazione abbia già determinato il membro specifico da richiamare, eventualmente applicando la risoluzione dell'overload a un set di membri della funzione candidata.

Ai fini della descrizione del processo di chiamata, i membri della funzione sono suddivisi in due categorie:

  • Membri della funzione statica. Si tratta di metodi statici, funzioni di accesso alle proprietà statiche e operatori definiti dall'utente. I membri della funzione statica sono sempre non virtuali.
  • Membri della funzione dell'istanza. Si tratta di metodi di istanza, costruttori di istanze, funzioni di accesso alle proprietà dell'istanza e funzioni di accesso dell'indicizzatore. I membri di funzione di istanza sono non virtuali o virtuali e vengono sempre richiamati su una determinata istanza. L'istanza viene calcolata da un'espressione di istanza e diventa accessibile all'interno del membro della funzione come this (§12.8.14). Per un costruttore di istanza, l'espressione di istanza viene considerata l'oggetto appena allocato.

L'elaborazione in fase di esecuzione di una chiamata a un membro di funzione è costituita dai passaggi seguenti, in cui M è il membro della funzione e, se M è un membro dell'istanza, E è l'espressione di istanza:

  • Se M è un membro di funzione statico:

    • L'elenco di argomenti viene valutato come descritto in §12.6.2.
    • M viene richiamato.
  • In caso contrario, se il tipo di E è un Vdi tipo valore e M viene dichiarato o sottoposto a override in V:

    • E viene valutato. Se questa valutazione causa un'eccezione, non vengono eseguiti altri passaggi. Per un costruttore di istanza, questa valutazione è costituita dall'allocazione della memoria (in genere da uno stack di esecuzione) per il nuovo oggetto. In questo caso E viene classificato come variabile.
    • Se E non è classificato come variabile o se V non è un tipo di struct readonly (§16.2.2) e E è uno dei seguenti:
      • un parametro di input (§15.6.2.3.2) o
      • un campo readonly (§15.5.3) o
      • una variabile di riferimento readonly o ritorno (§9.7)

    viene quindi creata una variabile locale temporanea del tipo di Ee il valore di E viene assegnato a tale variabile. E viene quindi riclassificata come riferimento a tale variabile locale temporanea. La variabile temporanea è accessibile come this all'interno di M, ma non in altro modo. Pertanto, solo quando è possibile scrivere E è possibile che il chiamante osservi le modifiche apportate M a this.

    • L'elenco di argomenti viene valutato come descritto in §12.6.2.
    • M viene richiamato. La variabile a cui fa riferimento E diventa la variabile a cui fa riferimento this.
  • Altrimenti:

    • E viene valutato. Se questa valutazione causa un'eccezione, non vengono eseguiti altri passaggi.
    • L'elenco di argomenti viene valutato come descritto in §12.6.2.
    • Se il tipo di E è un value_type, viene eseguita una conversione boxing (§10.2.9) per convertire E in un class_type, e E viene considerato essere di quel class_type nei passaggi seguenti. Se il value_type è un enum_type, il class_type è System.Enum; in caso contrario, è System.ValueType.
    • Il valore di E è controllato per verificarne la validità. Se il valore di E è Null, viene generata una System.NullReferenceException e non vengono eseguiti altri passaggi.
    • L'implementazione del membro della funzione da richiamare viene determinata:
      • Se il tipo di data e ora di associazione di E è un'interfaccia, il membro della funzione da richiamare è l'implementazione di M fornito dal tipo di runtime dell'istanza a cui fa riferimento E. Questo membro della funzione viene determinato applicando le regole di mapping dell'interfaccia (§18.6.5) per determinare l'implementazione di M fornita dal tipo di runtime dell'istanza a cui fa riferimento E.
      • In caso contrario, se M è un membro di funzione virtuale, il membro della funzione da richiamare è l'implementazione di M fornita dal tipo di runtime dell'istanza a cui fa riferimento E. Questo membro della funzione viene determinato applicando le regole per determinare l'implementazione più derivata (§15.6.4) di M rispetto al tipo di runtime dell'istanza a cui fa riferimento E.
      • In caso contrario, M è un membro di funzione non virtuale e il membro della funzione da richiamare è M stesso.
    • Viene richiamata l'implementazione del membro della funzione determinata nel passaggio precedente. L'oggetto a cui fa riferimento E diventa l'oggetto a cui fa riferimento questo oggetto.

Il risultato della chiamata di un costruttore di istanza (§12.8.17.2) è il valore creato. Il risultato della chiamata di qualsiasi altro membro della funzione è il valore, se presente, restituito (§13.10.5) dal corpo.

12.6.6.2 Chiamate su istanze boxed

Un membro di funzione implementato in un value_type può essere richiamato tramite un'istanza racchiusa di value_type nelle situazioni seguenti:

  • Quando il membro della funzione è un override di un metodo ereditato dal tipo class_type e viene invocato tramite un'espressione di istanza di quel tipo class_type.

    Nota: il class_type sarà sempre uno tra System.Object, System.ValueType o System.Enumnota finale

  • Quando il membro della funzione è un'implementazione di un membro della funzione di interfaccia e viene richiamato tramite un'espressione di istanza di un interface_type.
  • Quando il membro della funzione viene richiamato tramite un delegato.

In queste situazioni, si considera che l'istanza boxed contenga una variabile del value_type, e questa diventa la variabile a cui si fa riferimento nella chiamata del membro della funzione.

Nota: in particolare, ciò significa che quando un membro della funzione viene richiamato in un'istanza boxed, è possibile che il membro della funzione modifichi il valore contenuto nell'istanza boxed. nota finale

12.7 Decostruzione

La decostruzione è un processo in cui un'espressione viene trasformata in una tupla di singole espressioni. La decostruzione viene usata quando la destinazione di un'assegnazione semplice è un'espressione di tupla, al fine di ottenere valori da assegnare a ciascuno degli elementi della tupla.

Un'espressione E viene decostruita in un'espressione a tupla con n elementi nel modo seguente:

  • Se E è un'espressione di tupla con n elementi, il risultato della decomposizione è l'espressione E stessa.
  • In caso contrario, se E ha un tipo di tupla (T1, ..., Tn) con n elementi, allora E viene valutato in una variabile temporanea __ve il risultato della decostruzione è l'espressione (__v.Item1, ..., __v.Itemn).
  • In caso contrario, se l'espressione E.Deconstruct(out var __v1, ..., out var __vn) viene risolta in fase di compilazione in un'istanza o in un metodo di estensione univoco, tale espressione viene valutata e il risultato della decostruzione è l'espressione (__v1, ..., __vn). Tale metodo viene definito distruttore .
  • In caso contrario, non è possibile destruire E.

In questo caso, __v e __v1, ..., __vn fanno riferimento a variabili temporanee invisibili e inaccessibili, altrimenti.

Nota: non è possibile decostruire un'espressione di tipo dynamic. nota finale

12.8 Espressioni primarie

12.8.1 Generale

Le espressioni primarie includono le forme più semplici di espressioni.

primary_expression
    : primary_no_array_creation_expression
    | array_creation_expression
    ;

primary_no_array_creation_expression
    : literal
    | interpolated_string_expression
    | simple_name
    | parenthesized_expression
    | tuple_expression
    | member_access
    | null_conditional_member_access
    | invocation_expression
    | element_access
    | null_conditional_element_access
    | this_access
    | base_access
    | post_increment_expression
    | post_decrement_expression
    | null_forgiving_expression
    | object_creation_expression
    | delegate_creation_expression
    | anonymous_object_creation_expression
    | typeof_expression
    | sizeof_expression
    | checked_expression
    | unchecked_expression
    | default_value_expression
    | nameof_expression    
    | anonymous_method_expression
    | pointer_member_access     // unsafe code support
    | pointer_element_access    // unsafe code support
    | stackalloc_expression
    ;

Nota: queste regole grammaticali non sono pronte per ANTLR perché fanno parte di un set di regole mutuamente ricorsive a sinistra (primary_expression, primary_no_array_creation_expression, member_access, invocation_expression, element_access, post_increment_expression, post_decrement_expression, null_forgiving_expression, pointer_member_access e pointer_element_access) non gestite da ANTLR. Le tecniche standard possono essere usate per trasformare la grammatica per rimuovere la ricorsione reciproca a sinistra. Questa operazione non è stata eseguita perché non tutte le strategie di analisi lo richiedono (ad esempio, un parser LALR non lo farebbe) e in questo modo si offusca la struttura e la descrizione. nota finale

pointer_member_access (§23.6.3) e pointer_element_access (§23.6.4) sono disponibili solo nel codice non sicuro (§23).

Le espressioni primarie si dividono tra array_creation_expressione le primary_no_array_creation_expression. Trattare array_creation_expression in questo modo, anziché elencarlo insieme ad altre forme di espressione semplici, consente alla grammatica di non consentire codice potenzialmente confuso, ad esempio

object o = new int[3][1];

che altrimenti verrebbe interpretato come

object o = (new int[3])[1];

12.8.2 Valori letterali

Un primary_expression che consiste di un letterale (§6.4.5) è classificato come valore.

12.8.3 Espressioni di stringa interpolate

Un interpolated_string_expression è costituito da $, $@o @$, immediatamente seguito da testo all'interno di " caratteri. All'interno del testo citato ci sono zero o più interpolazioni delimitate da caratteri { e }, ognuno dei quali racchiude un'espressione e specifiche di formattazione facoltative.

Le espressioni di stringa interpolate hanno due forme: regolare (interpolated_regular_string_expression) e verbatim (interpolated_verbatim_string_expression), che sono lessicalmente simili, ma differiscono semanticamente dalle due forme di valori letterali stringa (§6.4.5.6).

interpolated_string_expression
    : interpolated_regular_string_expression
    | interpolated_verbatim_string_expression
    ;

// interpolated regular string expressions

interpolated_regular_string_expression
    : Interpolated_Regular_String_Start Interpolated_Regular_String_Mid?
      ('{' regular_interpolation '}' Interpolated_Regular_String_Mid?)*
      Interpolated_Regular_String_End
    ;

regular_interpolation
    : expression (',' interpolation_minimum_width)?
      Regular_Interpolation_Format?
    ;

interpolation_minimum_width
    : constant_expression
    ;

Interpolated_Regular_String_Start
    : '$"'
    ;

// the following three lexical rules are context sensitive, see details below

Interpolated_Regular_String_Mid
    : Interpolated_Regular_String_Element+
    ;

Regular_Interpolation_Format
    : ':' Interpolated_Regular_String_Element+
    ;

Interpolated_Regular_String_End
    : '"'
    ;

fragment Interpolated_Regular_String_Element
    : Interpolated_Regular_String_Character
    | Simple_Escape_Sequence
    | Hexadecimal_Escape_Sequence
    | Unicode_Escape_Sequence
    | Open_Brace_Escape_Sequence
    | Close_Brace_Escape_Sequence
    ;

fragment Interpolated_Regular_String_Character
    // Any character except " (U+0022), \\ (U+005C),
    // { (U+007B), } (U+007D), and New_Line_Character.
    : ~["\\{}\u000D\u000A\u0085\u2028\u2029]
    ;

// interpolated verbatim string expressions

interpolated_verbatim_string_expression
    : Interpolated_Verbatim_String_Start Interpolated_Verbatim_String_Mid?
      ('{' verbatim_interpolation '}' Interpolated_Verbatim_String_Mid?)*
      Interpolated_Verbatim_String_End
    ;

verbatim_interpolation
    : expression (',' interpolation_minimum_width)?
      Verbatim_Interpolation_Format?
    ;

Interpolated_Verbatim_String_Start
    : '$@"'
    | '@$"'
    ;

// the following three lexical rules are context sensitive, see details below

Interpolated_Verbatim_String_Mid
    : Interpolated_Verbatim_String_Element+
    ;

Verbatim_Interpolation_Format
    : ':' Interpolated_Verbatim_String_Element+
    ;

Interpolated_Verbatim_String_End
    : '"'
    ;

fragment Interpolated_Verbatim_String_Element
    : Interpolated_Verbatim_String_Character
    | Quote_Escape_Sequence
    | Open_Brace_Escape_Sequence
    | Close_Brace_Escape_Sequence
    ;

fragment Interpolated_Verbatim_String_Character
    : ~["{}]    // Any character except " (U+0022), { (U+007B) and } (U+007D)
    ;

// lexical fragments used by both regular and verbatim interpolated strings

fragment Open_Brace_Escape_Sequence
    : '{{'
    ;

fragment Close_Brace_Escape_Sequence
    : '}}'
    ;

Sei delle regole lessicali definite in precedenza sono sensibili al contesto, come indicato di seguito:

regola Requisiti Contestuali
Interpolated_Regular_String_Mid Riconosciuto solo dopo un Interpolated_Regular_String_Start, fra le eventuali interpolazioni successive e prima del Interpolated_Regular_String_Endcorrispondente.
Regular_Interpolation_Format Riconosciuto solo all'interno di un regular_interpolation e quando i due punti iniziali (:) non sono annidati all'interno di qualsiasi tipo di parentesi (tonde/graffe/quadre).
Interpolated_Regular_String_End Riconosciuto solo dopo un Interpolated_Regular_String_Start e solo se eventuali token intermedi sono Interpolated_Regular_String_Mido token che possono far parte di regular_interpolations, inclusi i token per qualsiasi interpolated_regular_string_expressioncontenuti in tali interpolazioni.
Interpolated_Verbatim_String_MidVerbatim_Interpolation_FormatInterpolated_Verbatim_String_End Il riconoscimento di queste tre regole segue quello delle regole corrispondenti sopra indicate con ogni regola di grammatica regolare sostituita dalla corrispondente verbatim uno.

Nota: le regole precedenti sono sensibili al contesto poiché le relative definizioni si sovrappongono a quelle di altri token nel linguaggio. nota finale

Nota: la grammatica precedente non è pronta per ANTLR a causa delle regole lessicali sensibili al contesto. Come con altri generatori lexer ANTLR supporta regole lessicali sensibili al contesto, ad esempio usando le relative modalità lessicali , ma si tratta di un dettaglio di implementazione e pertanto non fa parte di questa specifica. nota finale

Un interpolated_string_expression è classificato come un valore. Se viene immediatamente convertito in System.IFormattable o System.FormattableString con una conversione implicita di stringa interpolata (§10.2.5), l'espressione stringa interpolata ha tale tipo. In caso contrario, ha il tipo string.

Nota: le differenze tra i possibili tipi di un interpolated_string_expression possono essere determinate dalla documentazione per System.String (§C.2) e System.FormattableString (§C.3). nota finale

Il significato di un'interpolazione, sia regular_interpolation che verbatim_interpolation, si riferisce a formattare il valore dell'espressione come string, in base al formato specificato dal Regular_Interpolation_Format o Verbatim_Interpolation_Format, oppure in base a un formato di default per il tipo di espressione . La stringa formattata viene quindi modificata dal interpolation_minimum_width, se presente, per produrre il string finale da interpolare nell'espressione di stringa interpolata .

Nota: il modo in cui viene determinato il formato predefinito per un tipo è dettagliato nella documentazione per System.String (§C.2) e System.FormattableString (§C.3). Le descrizioni dei formati standard, identici per Regular_Interpolation_Format e Verbatim_Interpolation_Format, sono disponibili nella documentazione relativa a System.IFormattable (§C.4) e in altri tipi della libreria standard (§C). nota finale

In una interpolation_minimum_width, l'constant_expression deve avere una conversione implicita in int. Si lasci che la larghezza del campo sia il valore assoluto di questa espressione_costante e che l'allineamento sia il segno (positivo o negativo) del valore di questa espressione_costante:

  • Se il valore della larghezza del campo è minore o uguale alla lunghezza della stringa formattata, la stringa formattata non viene modificata.
  • In caso contrario, la stringa formattata viene riempita con spazi vuoti in modo che la lunghezza sia uguale alla larghezza del campo:
    • Se l'allineamento è positivo, la stringa formattata è allineata a destra anteponendo la spaziatura interna,
    • In caso contrario, è allineato a sinistra aggiungendo la spaziatura interna.

Il significato complessivo di un interpolated_string_expression, inclusa la formattazione e la spaziatura interna delle interpolazioni precedenti, è definita da una conversione dell'espressione in una chiamata al metodo: se il tipo dell'espressione è System.IFormattable o System.FormattableString tale metodo è System.Runtime.CompilerServices.FormattableStringFactory.Create (§C.3) che restituisce un valore di tipo System.FormattableString; in caso contrario, il tipo deve essere string e il metodo è string.Format (§C.2) che restituisce un valore di tipo string.

In entrambi i casi, l'elenco di argomenti della chiamata è costituito da una stringa letterale di formato con specifiche di formato e per ciascuna interpolazione, e un argomento per ogni espressione corrispondente alle specifiche di formato.

La stringa letterale di formato viene costruita come segue, dove N è il numero di interpolazioni nell'espressione di stringa interpolata . La stringa letterale di formato è costituita da, in ordine:

  • Caratteri dei Interpolated_Regular_String_Start o dei Interpolated_Verbatim_String_Start
  • I caratteri di Interpolated_Regular_String_Mid o Interpolated_Verbatim_String_Mid, se presenti
  • Quindi, se N ≥ 1 per ogni numero I da 0 a N-1:
    • Specifica segnaposto:
      • Carattere parentesi graffa sinistra ({)
      • Rappresentazione decimale di I
      • Quindi, se il regular_interpolation o il verbatim_interpolation corrispondente ha un interpolation_minimum_width, si aggiunge una virgola (,) seguita dalla rappresentazione decimale del valore del constant_expression
      • Caratteri del Regular_Interpolation_Format o Verbatim_Interpolation_Format, se presenti, del regular_interpolation o dell'verbatim_interpolation corrispondente
      • Carattere parentesi graffa destra (})
    • I caratteri del Interpolated_Regular_String_Mid o Interpolated_Verbatim_String_Mid immediatamente dopo l'interpolazione corrispondente, se presenti
  • Infine i caratteri del Interpolated_Regular_String_End o del Interpolated_Verbatim_String_End.

Gli argomenti successivi sono l'espressione s dalle interpolazioni, se presenti, in ordine.

Quando un interpolated_string_expression contiene più interpolazioni, le espressioni in tali interpolazioni vengono valutate in ordine testuale da sinistra a destra.

esempio:

In questo esempio vengono usate le funzionalità di specifica del formato seguenti:

  • la specifica di formato X che formatta i numeri interi come esadecimali maiuscoli,
  • il formato predefinito per un valore string è il valore stesso,
  • valori di allineamento positivi che giustificano a destra all'interno della larghezza minima del campo specificata,
  • valori di allineamento negativi che allineano a sinistra entro la larghezza minima del campo specificata,
  • costanti definite per il interpolation_minimum_widthe
  • che {{ e }} vengono formattati rispettivamente come { e }.

Dedito:

string text = "red";
int number = 14;
const int width = -4;

Allora:

Espressione di stringa interpolata equivalente a string valore
$"{text}" string.Format("{0}", text) "red"
$"{{text}}" string.Format("{{text}}) "{text}"
$"{ text , 4 }" string.Format("{0,4}", text) " red"
$"{ text , width }" string.Format("{0,-4}", text) "red "
$"{number:X}" string.Format("{0:X}", number) "E"
$"{text + '?'} {number % 3}" string.Format("{0} {1}", text + '?', number % 3) "red? 2"
$"{text + $"[{number}]"}" string.Format("{0}", text + string.Format("[{0}]", number)) "red[14]"
$"{(number==0?"Zero":"Non-zero")}" string.Format("{0}", (number==0?"Zero":"Non-zero")) "Non-zero"

esempio finale

12.8.4 Nomi semplici

Un simple_name è costituito da un identificatore, seguito facoltativamente da un elenco di argomenti di tipo:

simple_name
    : identifier type_argument_list?
    ;

Un simple_name ha la forma I o I<A₁, ..., Aₑ>, dove I è un singolo identificatore e I<A₁, ..., Aₑ> è una type_argument_listfacoltativa. Quando non viene specificato alcun type_argument_list, considerare e pari a zero. Il simple_name viene valutato e classificato come segue:

  • Se e è zero e il simple_name viene visualizzato all'interno di uno spazio di dichiarazione di variabile locale (§7.3) che contiene direttamente una variabile locale, un parametro o una costante con nome I, il simple_name fa riferimento a tale variabile locale, parametro o costante ed è classificato come variabile o valore.
  • Se e è zero e il simple_name viene visualizzato all'interno di una dichiarazione di metodo generico ma all'esterno degli attributi del relativo method_declaratione se tale dichiarazione include un parametro di tipo con nome I, il simple_name fa riferimento a tale parametro di tipo.
  • In caso contrario, per ogni tipo di istanza T (§15.3.2), si inizia con il tipo di istanza della dichiarazione di tipo che lo racchiude immediatamente, e si continua con il tipo di istanza di ogni dichiarazione di classe o struct che lo contiene (se presente):
    • Se e è zero e la dichiarazione di T include un parametro di tipo con nome I, il simple_name fa riferimento a tale parametro di tipo.
    • In caso contrario, se una ricerca di un membro (§12.5) di I in T con argomenti di tipo e produce una corrispondenza:
      • Se T è il tipo di istanza della classe o struttura immediatamente racchiudente e la ricerca individua uno o più metodi, il risultato è un gruppo di metodi con un'espressione di istanza associata a this. Se è stato specificato un elenco di argomenti di tipo, viene usato per chiamare un metodo generico (§12.8.10.2).
      • In caso contrario, se T è il tipo di istanza della classe o del tipo struct immediatamente circostante, se la ricerca identifica un membro di un'istanza e se il riferimento si verifica all'interno del blocco di un costruttore di istanza, di un metodo di istanza o di un accessore dell'istanza (§12.2.1), il risultato corrisponde a un accesso membro (§12.8.7) del modulo this.I. Questo problema può verificarsi solo quando e è zero.
      • In caso contrario, il risultato è uguale all'accesso a un membro (§12.8.7) della forma T.I o T.I<A₁, ..., Aₑ>.
  • In caso contrario, per ogni spazio dei nomi N, a partire dallo spazio dei nomi in cui si verifica il simple_name, continuando con ogni spazio dei nomi racchiuso (se presente) e terminando con lo spazio dei nomi globale, i passaggi seguenti vengono valutati fino a quando non viene individuata un'entità:
    • Se e è zero e I è il nome di uno spazio dei nomi in N, allora:
      • Se la posizione in cui si verifica il simple_name è racchiusa da una dichiarazione di namespace per N e la dichiarazione di namespace contiene un extern_alias_directive o un using_alias_directive che associa il nome I a uno spazio dei nomi o a un tipo, allora il simple_name è ambiguo e si verifica un errore in fase di compilazione.
      • In caso contrario, il simple_name fa riferimento allo spazio dei nomi denominato I in N.
    • In caso contrario, se N contiene un tipo accessibile avente il nome I e parametri di tipo e, allora:
      • Se e è zero e la posizione in cui si verifica il simple_name è racchiusa da una dichiarazione dello spazio dei nomi per N e la dichiarazione dello spazio dei nomi contiene un extern_alias_directive o un using_alias_directive che associa il nome I a uno spazio dei nomi o un tipo, il simple_name è ambiguo e si verifica un errore in fase di compilazione.
      • In caso contrario, il namespace_or_type_name fa riferimento al tipo costruito con gli argomenti di tipo specificati.
    • In caso contrario, se la posizione in cui si verifica il simple_name è racchiusa da una dichiarazione dello spazio dei nomi per N:
      • Se e è zero e la dichiarazione dello spazio dei nomi contiene un extern_alias_directive o un using_alias_directive che associa il nome I a uno spazio dei nomi o un tipo importato, il simple_name fa riferimento a tale spazio dei nomi o tipo.
      • In caso contrario, se gli spazi dei nomi importati dall'using_namespace_directivedella dichiarazione dello spazio dei nomi contengono esattamente un tipo con nome I e parametri di tipo e, il simple_name fa riferimento a quel tipo costruito con gli argomenti di tipo specificati.
      • In caso contrario, se gli spazi dei nomi importati dalla direttiva using_namespace della dichiarazione dello spazio dei nomi contengono più di un tipo avente il nome I e parametri di tipo e, il nome semplice risulta ambiguo e si verifica un errore in fase di compilazione.

    Nota: l'intero passaggio è esattamente parallelo al passaggio corrispondente nell'elaborazione di un namespace_or_type_name (§7.8). nota finale

  • In caso contrario, se e è zero e I è l'identificatore _, il simple_name è un semplicedi eliminazione , che è una forma di espressione di dichiarazione (§12.17).
  • In caso contrario, il simple_name non è definito e si verifica un errore in fase di compilazione.

12.8.5 Espressioni racchiuse tra parentesi

Un parenthesized_expression è costituito da un'espressione racchiusa tra parentesi.

parenthesized_expression
    : '(' expression ')'
    ;

Un'espressione tra parentesi viene valutata valutando l'espressione all'interno delle parentesi. Se l'espressione tra parentesi indica uno spazio dei nomi o un tipo, si verifica un errore in fase di compilazione. In caso contrario, il risultato del parenthesized_expression è il risultato della valutazione dell'espressione contenuta.

12.8.6 Espressioni di tupla

Un tuple_expression rappresenta una tupla ed è costituito da due o più espressioni, separate da virgole e opzionalmente denominate , racchiuse tra parentesi. Un deconstruction_expression è una sintassi abbreviata per una tupla contenente espressioni di dichiarazione tipizzate in modo implicito.

tuple_expression
    : '(' tuple_element (',' tuple_element)+ ')'
    | deconstruction_expression
    ;
    
tuple_element
    : (identifier ':')? expression
    ;
    
deconstruction_expression
    : 'var' deconstruction_tuple
    ;
    
deconstruction_tuple
    : '(' deconstruction_element (',' deconstruction_element)+ ')'
    ;

deconstruction_element
    : deconstruction_tuple
    | identifier
    ;

Una espressione tupla viene classificata come tupla.

Un deconstruction_expressionvar (e1, ..., en) è un'abbreviazione della tuple_expression(var e1, ..., var en) e segue lo stesso comportamento. Ciò si applica in modo ricorsivo a qualsiasi deconstruction_tupleannidato nel deconstruction_expression. Ogni identificatore annidato all'interno di un deconstruction_expression introduce quindi un'espressione di dichiarazione (§12.17). Di conseguenza, un deconstruction_expression può verificarsi solo sul lato sinistro di un'assegnazione semplice.

Un'espressione di tupla ha un tipo se e solo se ognuna delle sue espressioni di elemento Ei ha un tipo Ti. Il tipo deve essere un tipo di tupla della stessa arità dell'espressione di tupla, in cui ogni elemento è dato dai seguenti:

  • Se l'elemento della tupla nella posizione corrispondente ha un nome Ni, allora l'elemento del tipo della tupla deve essere Ti Ni.
  • In caso contrario, se Ei è nel formato Ni o E.Ni o E?.Ni, l'elemento del tipo di tupla deve essere Ti Ni, a meno che non uno dei blocchi seguenti:
    • Un altro elemento dell'espressione della tupla ha il nome Nio
    • Un altro elemento di tupla senza un nome ha un'espressione nella forma di un elemento di tupla Ni o E.Ni o E?.Ni.
    • Ni è del formato ItemX, in cui X è una sequenza di cifre decimali non avviate da0che potrebbero rappresentare la posizione di un elemento della tupla e X non rappresenta la posizione dell'elemento.
  • In caso contrario, l'elemento del tipo di tupla deve essere Ti.

Si valuta un'espressione di tupla valutando ciascuna delle sue espressioni di elemento in ordine da sinistra a destra.

Un valore di tupla può essere ottenuto da un'espressione di tupla convertendolo in un tipo di tupla (§10.2.13), riclassificandolo come valore (§12.2.2) o impostandolo come destinazione di un'assegnazione decostruttiva (§12.21.2).

esempio:

(int i, string) t1 = (i: 1, "One");
(long l, string) t2 = (l: 2, null);
var t3 = (i: 3, "Three");          // (int i, string)
var t4 = (i: 4, null);             // Error: no type

In questo esempio tutte e quattro le espressioni di tupla sono valide. I primi due, t1 e t2, non usano il tipo dell'espressione di tupla, ma applicano invece una conversione di tupla implicita. Nel caso di t2, la conversione implicita della tupla si basa sulle conversioni implicite da 2 a long e da null a string. La terza espressione di tupla ha un tipo (int i, string)e può quindi essere riclassificata come valore di tale tipo. La dichiarazione di t4, invece, è un errore: l'espressione di tupla non ha alcun tipo perché il secondo elemento non ha alcun tipo.

if ((x, y).Equals((1, 2))) { ... };

Questo esempio mostra che i tuple possono talvolta portare a più livelli di parentesi, soprattutto quando l'espressione a tuple è l'unico argomento in un'invocazione di metodo.

esempio finale

12.8.7 Accesso ai membri

12.8.7.1 Generale

Un member_access è costituito da un primary_expression, da un predefined_typeo da un qualified_alias_member, seguito da un token ".", seguito da un identificatore , seguito facoltativamente da un type_argument_list.

member_access
    : primary_expression '.' identifier type_argument_list?
    | predefined_type '.' identifier type_argument_list?
    | qualified_alias_member '.' identifier type_argument_list?
    ;

predefined_type
    : 'bool' | 'byte' | 'char' | 'decimal' | 'double' | 'float' | 'int'
    | 'long' | 'object' | 'sbyte' | 'short' | 'string' | 'uint' | 'ulong'
    | 'ushort'
    ;

La produzione qualified_alias_member è definita in §14.8.

Un member_access è nella forma E.I o nella forma E.I<A₁, ..., Aₑ>, dove E è un primary_expression, un predefined_type o un qualified_alias_member,I è un singolo identificatore e <A₁, ..., Aₑ> è una type_argument_listfacoltativa. Quando non viene specificato alcun type_argument_list, considerare e pari a zero.

Un member_access con una primary_expression di tipo dynamic è legato dinamicamente (§12.3.3). In questo caso, il compilatore classifica l'accesso al membro come accesso a una proprietà di tipo dynamic. Le regole seguenti per determinare il significato del member_access si applicano quindi in fase di esecuzione, utilizzando il tipo di tempo di esecuzione anziché il tipo di tempo di compilazione del primary_expression. Se questa classificazione in fase di esecuzione conduce a un gruppo di metodi, l'accesso ai membri sarà il primary_expression di un invocation_expression.

Il member_access viene valutato e classificato come segue:

  • Se e è zero e E è uno spazio dei nomi e E contiene uno spazio dei nomi annidato con nome I, il risultato è quello.
  • Altrimenti, se E è uno spazio dei nomi e E contiene un tipo accessibile con il nome I e K parametri di tipo, allora il risultato è quel tipo costruito con gli argomenti di tipo forniti.
  • Se E è classificato come tipo, se E non è un parametro di tipo e se una ricerca membro (§12,5) di I in E con parametri di tipo K produce una corrispondenza, E.I viene valutata e classificata come segue:

    Nota: quando il risultato di una tale ricerca di membro è un gruppo di metodi e K è zero, il gruppo di metodi può contenere metodi con parametri di tipo. In questo modo tali metodi possono essere considerati per l'inferenza dei parametri di tipo. nota finale

    • Se I identifica un tipo, il risultato è il tipo costruito con qualsiasi argomento di tipo.
    • Se I identifica uno o più metodi, il risultato è un gruppo di metodi senza espressione di istanza associata.
    • Se I identifica una proprietà statica, il risultato è un accesso alle proprietà senza espressione di istanza associata.
    • Se I identifica un campo statico:
      • Se il campo è di sola lettura e il riferimento si verifica all'esterno del costruttore statico della classe o dello struct in cui viene dichiarato il campo, il risultato è un valore, ovvero il valore del campo statico I in E.
      • In caso contrario, il risultato è una variabile, ovvero il campo statico I in E.
    • Se I identifica un evento statico:
      • Se il riferimento si verifica all'interno della classe o dello struct in cui viene dichiarato l'evento e l'evento è stato dichiarato senza event_accessor_declarations (§15.8.1), E.I viene elaborato esattamente come se I fosse un campo statico.
      • In caso contrario, il risultato è un accesso a eventi senza espressione di istanza associata.
    • Se I identifica una costante, il risultato è un valore, vale a dire il valore di tale costante.
    • Se I identifica un membro di enumerazione, il risultato è un valore, vale a dire il valore del membro di enumerazione.
    • In caso contrario, E.I è un riferimento membro non valido e si verifica un errore in fase di compilazione.
  • Se E è un accesso a una proprietà, un accesso a un indicizzatore, una variabile o un valore, il cui tipo è T, e una ricerca membro (§12,5) di I in T con argomenti di tipo K produce una corrispondenza, allora E.I viene valutato e classificato come segue:
    • In primo luogo, se E è un accesso a una proprietà o a un indicizzatore, viene ottenuto il valore della proprietà o dell'indicizzatore (§12.2.2) ed E viene riclassificato come valore.
    • Se I identifica uno o più metodi, il risultato è un gruppo di metodi con un'espressione di istanza associata di E.
    • Se I identifica una proprietà dell'istanza, il risultato è un accesso alle proprietà con un'espressione di istanza associata di E e un tipo associato che corrisponde al tipo della proprietà. Se T è un tipo di classe, il tipo associato viene selezionato dalla prima dichiarazione o override della proprietà trovata a partire da T, cercando attraverso le sue classi base.
    • Se T è un class_type e I identifica un campo dell'istanza di tale class_type:
      • Se il valore di E è null, viene generata una System.NullReferenceException.
      • In caso contrario, se il campo è di sola lettura e il riferimento si verifica all'esterno di un costruttore di istanza della classe in cui viene dichiarato il campo, il risultato è un valore, ovvero il valore del campo I nell'oggetto a cui fa riferimento E.
      • In caso contrario, il risultato è una variabile, ovvero il campo I nell'oggetto a cui fa riferimento E.
    • Se T è un struct_type e I identifica un campo di istanza del suddetto struct_type.
      • Se E è un valore o se il campo è di sola lettura e il riferimento si verifica all'esterno di un costruttore di istanza dello struct in cui viene dichiarato il campo, il risultato è un valore, ovvero il valore del campo I nell'istanza dello struct specificata da E.
      • In caso contrario, il risultato è una variabile, ovvero il campo I nell'istanza dello struct specificato da E.
    • Se I identifica un evento dell'istanza:
      • Se il riferimento si verifica all'interno della classe o dello struct in cui viene dichiarato l'evento e l'evento è stato dichiarato senza event_accessor_declarations (§15.8.1) e il riferimento non si verifica come lato sinistro di a += o -= operatore, E.I viene elaborato esattamente come se I fosse un campo di istanza.
      • In caso contrario, il risultato è un accesso agli eventi con un'espressione di istanza associata di E.
  • In caso contrario, si tenta di gestire E.I come chiamata al metodo di estensione (§12.8.10.3). Se questo fallisce, E.I è un riferimento membro non valido e si verifica un errore di tempo di binding.

12.8.7.2 Nomi semplici e nomi di tipo identici

In un accesso membro del modulo E.I, se E è un singolo identificatore e se il significato di E come simple_name (§12.8.4) è una costante, un campo, una proprietà, una variabile locale o un parametro con lo stesso tipo del significato di E come type_name (§7.8.1), allora sono consentiti entrambi i possibili significati di E. La ricerca del membro di E.I non è mai ambigua, poiché I deve necessariamente essere un membro del tipo E in entrambi i casi. In altre parole, la regola consente semplicemente l'accesso ai membri statici e ai tipi annidati di E dove altrimenti si sarebbe verificato un errore in fase di compilazione.

esempio:

struct Color
{
    public static readonly Color White = new Color(...);
    public static readonly Color Black = new Color(...);
    public Color Complement() => new Color(...);
}

class A
{
    public «Color» Color;              // Field Color of type Color

    void F()
    {
        Color = «Color».Black;         // Refers to Color.Black static member
        Color = Color.Complement();  // Invokes Complement() on Color field
    }

    static void G()
    {
        «Color» c = «Color».White;       // Refers to Color.White static member
    }
}

Solo a scopo espositivo, all'interno della classe A, le occorrenze dell'identificatore Color che fanno riferimento al tipo Color sono delimitate da «...», mentre quelle che fanno riferimento al campo Color non lo sono.

esempio finale

12.8.8 Accesso ai membri condizionale nullo

Un null_conditional_member_access è una versione condizionale di member_access (§12.8.7) ed è un errore di data e ora di associazione se il tipo di risultato è void. Per un'espressione condizionale nulla in cui il tipo di risultato può essere void, consultare (§12.8.11).

Un null_conditional_member_access è costituito da un primary_expression seguito dai due token "?" e ".", seguito da un identificatore con un type_argument_listfacoltativo, seguito da zero o più dependent_access, uno dei quali può essere preceduto da un null_forgiving_operator.

null_conditional_member_access
    : primary_expression '?' '.' identifier type_argument_list?
      (null_forgiving_operator? dependent_access)*
    ;
    
dependent_access
    : '.' identifier type_argument_list?    // member access
    | '[' argument_list ']'                 // element access
    | '(' argument_list? ')'                // invocation
    ;

null_conditional_projection_initializer
    : primary_expression '?' '.' identifier type_argument_list?
    ;

Un'espressione null_conditional_member_accessE è nel formato P?.A. Il significato di E è determinato nel modo seguente:

  • Se il tipo di P è un tipo di valore annullabile:

    Sia T il tipo di P.Value.A.

    • Se T è un parametro di tipo di cui non si sa se sia un tipo di riferimento o un tipo di valore non annullabile, si verifica un errore in fase di compilazione.

    • Se T è un tipo valore non nullable, il tipo di E è T?e il significato di E è lo stesso del significato di:

      ((object)P == null) ? (T?)null : P.Value.A
      

      Ad eccezione del fatto che P viene valutato una sola volta.

    • In caso contrario, il tipo di E è Te il significato di E corrisponde al significato di:

      ((object)P == null) ? (T)null : P.Value.A
      

      Ad eccezione del fatto che P viene valutato una sola volta.

  • Altrimenti:

    Sia T il tipo dell'espressione P.A.

    • Se T è un parametro di tipo di cui non si sa se sia un tipo di riferimento o un tipo di valore non annullabile, si verifica un errore in fase di compilazione.

    • Se T è un tipo valore non nullable, il tipo di E è T?e il significato di E è lo stesso del significato di:

      ((object)P == null) ? (T?)null : P.A
      

      Ad eccezione del fatto che P viene valutato una sola volta.

    • In caso contrario, il tipo di E è Te il significato di E corrisponde al significato di:

      ((object)P == null) ? (T)null : P.A
      

      Ad eccezione del fatto che P viene valutato una sola volta.

Nota: in un'espressione del formato:

P?.A₀?.A₁

se P restituisce null non vengono valutati né A₀A₁. Lo stesso vale se un'espressione è una sequenza di operazioni di null_conditional_member_access o null_conditional_element_access§12.8.13.

nota finale

Un null_conditional_projection_initializer è una restrizione di null_conditional_member_access e ha la stessa semantica. Si verifica solo come inizializzatore di proiezione in un'espressione di creazione di oggetti anonima (§12.8.17.7).

12.8.9 Espressioni che ignorano i null

12.8.9.1 Generale

Il valore, il tipo, la classificazione (§12.2) e il contesto sicuro (§16.4.12) è il valore, il tipo, la classificazione e il contesto sicuro del relativo primary_expression.

null_forgiving_expression
    : primary_expression null_forgiving_operator
    ;

null_forgiving_operator
    : '!'
    ;

Nota: Gli operatori null-forgiving e di negazione logica prefisso (§12.9.4), sebbene rappresentati dallo stesso token lessicale (!), sono distinti. Solo quest'ultimo può essere sovrascritto (§15.10), la definizione dell'operatore null-forgiving è fissa. nota finale

Si tratta di un errore in fase di compilazione per applicare l'operatore null-forgiving più volte alla stessa espressione, indipendentemente dalle parentesi intermedie.

esempio: i seguenti elementi non sono validi:

var p = q!!;            // error: applying null_forgiving_operator more than once
var s = ( ( m(t) ! ) )! // error: null_forgiving_operator applied twice to m(t)

esempio finale

Il resto di questa sottoclausa e le seguenti sottoclause di pari livello sono normative condizionalmente.

Un compilatore che esegue l'analisi statica dello stato Null (§8.9.5) deve essere conforme alla specifica seguente.

L'operatore null-forgiving è una pseudooperazione in fase di compilazione usata per segnalare l'analisi statica dello stato null di un compilatore. Ha due usi: per ignorare la determinazione di un compilatore che un'espressione potrebbe essere nulla; e per ignorare un avviso emesso da un compilatore relativo alla nullabilità.

L'applicazione dell'operatore null-forgiving a un'espressione per cui l'analisi dello stato Null statico di un compilatore non genera avvisi non è un errore.

12.8.9.2 Sovrascrittura di una determinazione "potenzialmente nulla"

In alcuni casi, l'analisi statica dello stato Null di un compilatore può determinare che un'espressione ha lo stato Null forse null e genera un avviso di diagnostica quando altre informazioni indicano che l'espressione non può essere Null. L'applicazione dell'operatore null-forgiving a tale espressione informa l'analisi dello stato nullo statico del compilatore che lo stato nullo è in e non nullo in; ciò impedisce l'avviso diagnostico e potrebbe influenzare eventuali analisi in corso.

Esempio: Considera quanto segue:

#nullable enable
public static void M()
{
    Person? p = Find("John");                  // returns Person?
    if (IsValid(p))
    {
       Console.WriteLine($"Found {p!.Name}");  // p can't be null
    }
}

public static bool IsValid(Person? person) =>
    person != null && person.Name != null;

Se IsValid restituisce true, p può essere dereferenziato in modo sicuro per accedere alla relativa proprietà Name, e l'avviso "dereferenziazione di un valore eventualmente Null" può essere soppresso tramite !.

esempio finale

Esempio: L'operatore null-forgiving deve essere usato con cautela, considera:

#nullable enable
int B(int? x)
{
    int y = (int)x!; // quash warning, throw at runtime if x is null
    return y;
}

In questo caso, l'operatore null-forgiving viene applicato a un tipo di valore e sopprime qualsiasi avviso su x. Se tuttavia x è null in fase di esecuzione, verrà generata un'eccezione perché non è possibile eseguire il cast di null a int.

esempio finale

12.8.9.3 Override di altri avvisi di analisi Null

Oltre a eseguire l'override di potrebbe essere null determinazioni come indicato in precedenza, potrebbero verificarsi altre circostanze in cui si desidera eseguire l'override dell'analisi statica dello stato Null di un compilatore per determinare che un'espressione richiede uno o più avvisi. L'applicazione dell'operatore null-forgiving a un'espressione di questo tipo richiede che un compilatore non emetta alcun avviso per l'espressione. In risposta un compilatore può scegliere di non generare avvisi e può anche modificarne l'ulteriore analisi.

Esempio: Considera quanto segue:

#nullable enable
public static void Assign(out string? lv, string? rv) { lv = rv; }

public string M(string? t)
{
    string s;
    Assign(out s!, t ?? "«argument was null»");
    return s;
}

I tipi dei parametri del metodo Assign, lv & rv, sono string?, con lv come parametro di output, e il metodo esegue un'assegnazione semplice.

Il metodo M passa la variabile s, di tipo string, come parametro di output di Assign. Il compilatore emette un avviso dato che s non è una variabile nullable. Dato che il secondo argomento di Assignnon può essere Null, viene usato l'operatore null-forgiving per annullare l'avviso.

esempio finale

Fine del testo condizionatamente normativo.

12.8.10 Espressioni di chiamata

12.8.10.1 Generale

Un invocation_expression viene usato per richiamare un metodo.

invocation_expression
    : primary_expression '(' argument_list? ')'
    ;

Il primary_expression può essere un null_forgiving_expression soltanto se ha un delegate_type.

Un invocation_expression è associato dinamicamente (§12.3.3) se almeno una delle condizioni seguenti:

  • L'primary_expression ha un tipo in fase di compilazione dynamic.
  • Almeno un argomento del argument_list facoltativo ha un tipo in fase di compilazione dynamic.

In questo caso, il compilatore classifica il invocation_expression come valore di tipo dynamic. Le regole seguenti per determinare il significato del invocation_expression vengono quindi applicate in fase di esecuzione, usando il tipo di runtime anziché il tipo in fase di compilazione di quelli del primary_expression e gli argomenti che hanno il tipo in fase di compilazione dynamic. Se il primary_expression non dispone di un tipo in fase di compilazione dynamic, la chiamata al metodo viene sottoposta a un controllo in fase di compilazione limitato come descritto in §12.6.5.

Il primary_expression di un invocation_expression deve essere un gruppo di metodi o un valore di un delegate_type. Se il primary_expression è un gruppo di metodi, il invocation_expression è una chiamata al metodo (§12.8.10.2). Se il primary_expression è un valore di delegate_type, il invocation_expression è una chiamata di delegato (§12.8.10.4). Se il primary_expression non è né un gruppo di metodi né un valore di un delegate_type, si verifica un errore di associazione durante il tempo di compilazione.

Il argument_list facoltativo (§12.6.2) fornisce valori o riferimenti a variabili per i parametri del metodo.

Il risultato della valutazione di un invocation_expression viene classificato come segue:

  • Se il invocation_expression richiama un metodo senza valore restituito (§15.6.1) o un delegato senza valore restituito, il risultato è nulla. Un'espressione classificata come nulla è consentita solo nel contesto di un statement_expression (§13.7) o come corpo di un lambda_expression (§12.19). In caso contrario, si verifica un errore al momento del binding.
  • In caso contrario, se il invocation_expression richiama un metodo che restituisce per riferimento (§15.6.1) o un delegato che restituisce per riferimento, il risultato è una variabile del tipo associato al tipo di ritorno del metodo o del delegato. Se l'invocazione riguarda un metodo di istanza e il ricevitore è di tipo classe T, il tipo associato viene selezionato a partire dalla prima dichiarazione o override del metodo trovati iniziando con T e scandendo le sue classi di base.
  • In caso contrario, il invocation_expression richiama un metodo returns-by-value (§15.6.1) o un delegato returns-by-value, e il risultato è un valore, con un tipo associato al tipo restituito del metodo o del delegato. Se l'invocazione riguarda un metodo di istanza e il ricevitore è di tipo classe T, il tipo associato viene selezionato a partire dalla prima dichiarazione o override del metodo trovati iniziando con T e scandendo le sue classi di base.

Chiamate al metodo 12.8.10.2

Per una chiamata al metodo, il primary_expression del invocation_expression deve essere un gruppo di metodi. Il gruppo di metodi identifica il metodo da invocare o l'insieme di metodi sovraccarichi da cui scegliere un metodo specifico da invocare. In quest'ultimo caso, la determinazione del metodo specifico da richiamare si basa sul contesto fornito dai tipi degli argomenti nella argument_list.

L'elaborazione in fase di associazione di una chiamata al metodo del modulo M(A), dove M è un gruppo di metodi (possibilmente incluso un type_argument_list) e A è un argument_listfacoltativo , è costituito dai passaggi seguenti:

  • Viene costruito l'insieme dei metodi candidati per l'invocazione del metodo. Per ogni metodo F associato al gruppo di metodi M:
    • Se F non è generico, F è un candidato quando:
      • M non dispone di un elenco di argomenti di tipo e
      • F è applicabile per quanto riguarda A (§12.6.4.2).
    • Se F è generico e M non dispone di un elenco di argomenti di tipo, F è un candidato quando:
      • Inferenza del tipo (§12.6.3) riesce, inferendo un elenco di argomenti di tipo per la chiamata e
      • Quando gli argomenti di tipo dedotti vengono sostituiti con i parametri del tipo di metodo corrispondenti, tutti i tipi costruiti nell'elenco di parametri di F soddisfano i vincoli (§8.4.5) e l'elenco di parametri di F è applicabile per quanto riguarda A (§12.6.4.2)
    • Se F è generico e M include un elenco di argomenti di tipo, F è un candidato quando:
      • F ha lo stesso numero di parametri di tipo del metodo presenti nella lista degli argomenti di tipo.
      • Quando gli argomenti di tipo vengono sostituiti per i parametri del tipo di metodo corrispondenti, tutti i tipi costruiti nell'elenco di parametri di F soddisfano i relativi vincoli (§8.4.5) e l'elenco di parametri di F è applicabile per quanto riguarda A (§12.6.4.2).
  • Il set di metodi candidati viene ridotto in modo da contenere solo i metodi dai tipi più derivati: per ogni metodo C.F nel set, dove C è il tipo in cui viene dichiarato il metodo F, tutti i metodi dichiarati in un tipo di base di C vengono rimossi dal set. Inoltre, se C è un tipo di classe diverso da object, tutti i metodi dichiarati in un tipo di interfaccia vengono rimossi dal set.

    Nota: quest'ultima regola ha effetto solo quando il gruppo di metodi è il risultato di una ricerca membro su un parametro di tipo con una classe base efficace diversa da object e un set di interfacce non vuoto. nota finale

  • Se il set risultante di metodi candidati è vuoto, vengono abbandonate ulteriori elaborazioni lungo i passaggi seguenti e viene invece effettuato un tentativo di elaborare la chiamata come chiamata al metodo di estensione (§12.8.10.3). Se questo fallisce, non esistono metodi applicabili e si verifica un errore di tempo di associazione.
  • Il metodo migliore del set di metodi candidati viene identificato usando le regole di risoluzione dell'overload di §12.6.4. Se non è possibile identificare un singolo metodo migliore, la chiamata al metodo è ambigua e si verifica un errore di data e ora di associazione. Quando si esegue la risoluzione dell'overload, i parametri di un metodo generico vengono presi in considerazione dopo aver sostituito gli argomenti di tipo (forniti o dedotti) con i parametri corrispondenti del tipo di metodo.

Dopo aver selezionato e convalidato un metodo in fase di associazione in base ai passaggi precedenti, la chiamata effettiva in fase di esecuzione viene elaborata in base alle regole della chiamata del membro della funzione descritta in §12.6.6.

Nota: l'intuitivo effetto delle regole di risoluzione descritte in precedenza è il seguente: per individuare il metodo specifico richiamato da una invocazione di un metodo, iniziare con il tipo indicato dall'invocazione del metodo e proseguire lungo la catena di ereditarietà fino a quando non viene trovata almeno una dichiarazione di metodo applicabile e accessibile che non sia sovrascrivibile. Eseguire quindi l'inferenza del tipo e la risoluzione dell'overload sul set di metodi applicabili, accessibili e non di override dichiarati in tale tipo e richiamare il metodo selezionato. Se non è stato trovato alcun metodo, provare invece a elaborare la chiamata come chiamata al metodo di estensione. nota finale

12.8.10.3 Invocazioni di metodi di estensione

In una chiamata al metodo (§12.6.6.2) di una delle forme

«expr» . «identifier» ( )  
«expr» . «identifier» ( «args» )  
«expr» . «identifier» < «typeargs» > ( )  
«expr» . «identifier» < «typeargs» > ( «args» )

se la normale elaborazione della chiamata non trova metodi applicabili, viene effettuato un tentativo di elaborare il costrutto come chiamata al metodo di estensione. Se «expr» o una qualsiasi delle «args» ha un tipo in fase di compilazione dynamic, i metodi di estensione non verranno applicati.

L'obiettivo è trovare il miglior type_nameC, in modo che la chiamata al metodo statico corrispondente possa avvenire.

C . «identifier» ( «expr» )  
C . «identifier» ( «expr» , «args» )  
C . «identifier» < «typeargs» > ( «expr» )  
C . «identifier» < «typeargs» > ( «expr» , «args» )

Un metodo di estensione Cᵢ.Mₑ è idoneo se:

  • Cᵢ è una classe non generica non annidata
  • Il nome di Mₑ è identificatore
  • Mₑ è accessibile e applicabile quando applicato agli argomenti come metodo statico, come illustrato in precedenza
  • Esiste un'identità implicita, un riferimento o una conversione di boxing da expr al tipo del primo parametro di Mₑ.

La ricerca di C procede nel modo seguente:

  • A partire dalla dichiarazione dello spazio dei nomi contenitore più vicina, continuando con ogni dichiarazione dello spazio dei nomi contenitore e terminando con l'unità di compilazione contenitore, vengono eseguiti tentativi successivi di trovare un set candidato di metodi di estensione:
    • Se l'unità di compilazione o dello spazio dei nomi specificata contiene direttamente dichiarazioni di tipo non generico Cᵢ con metodi di estensione idonei Mₑ, il set di tali metodi di estensione è il set candidato.
    • Se gli spazi dei nomi importati tramite direttive nello spazio dei nomi o nella unità di compilazione specificata contengono direttamente dichiarazioni di tipi non generici Cᵢ con metodi di estensione idonei Mₑ, il set di tali metodi di estensione è il set candidato.
  • Se non si trova alcun set candidato in una dichiarazione di spazio dei nomi circostante o in un'unità di compilazione, si verifica un errore di compilazione.
  • In caso contrario, al set candidato viene applicata la risoluzione dell'overload come descritto in §12.6.4. Se non viene trovato alcun singolo metodo migliore, si verifica un errore in fase di compilazione.
  • C è il tipo in cui il metodo migliore viene dichiarato come metodo di estensione.

Usando C come destinazione, la chiamata al metodo viene quindi elaborata come chiamata al metodo statico (§12.6.6).

Nota: a differenza della chiamata a un metodo di istanza, non viene generata alcuna eccezione quando expr restituisce un riferimento Null. Questo valore null viene invece passato al metodo di estensione come avverrebbe tramite un'invocazione di un metodo statico. Spetta all'implementazione del metodo di estensione decidere come rispondere a tale chiamata. nota finale

Le regole precedenti indicano che i metodi di istanza hanno la precedenza sui metodi di estensione, che i metodi di estensione disponibili nelle dichiarazioni dello spazio dei nomi interno hanno la precedenza sui metodi di estensione disponibili nelle dichiarazioni dello spazio dei nomi esterni e che i metodi di estensione dichiarati direttamente in uno spazio dei nomi hanno la precedenza sui metodi di estensione importati nello stesso spazio dei nomi con una direttiva dello spazio dei nomi using.

esempio:

public static class E
{
    public static void F(this object obj, int i) { }
    public static void F(this object obj, string s) { }
}

class A { }

class B
{
    public void F(int i) { }
}

class C
{
    public void F(object obj) { }
}

class X
{
    static void Test(A a, B b, C c)
    {
        a.F(1);            // E.F(object, int)
        a.F("hello");      // E.F(object, string)
        b.F(1);            // B.F(int)
        b.F("hello");      // E.F(object, string)
        c.F(1);            // C.F(object)
        c.F("hello");      // C.F(object)
    }
}

Nell'esempio Bmetodo ha la precedenza sul primo metodo di estensione e Cmetodo ha la precedenza su entrambi i metodi di estensione.

public static class C
{
    public static void F(this int i) => Console.WriteLine($"C.F({i})");
    public static void G(this int i) => Console.WriteLine($"C.G({i})");
    public static void H(this int i) => Console.WriteLine($"C.H({i})");
}

namespace N1
{
    public static class D
    {
        public static void F(this int i) => Console.WriteLine($"D.F({i})");
        public static void G(this int i) => Console.WriteLine($"D.G({i})");
    }
}

namespace N2
{
    using N1;

    public static class E
    {
        public static void F(this int i) => Console.WriteLine($"E.F({i})");
    }

    class Test
    {
        static void Main(string[] args)
        {
            1.F();
            2.G();
            3.H();
        }
    }
}

L'output di questo esempio è:

E.F(1)
D.G(2)
C.H(3)

D.G ha la precedenza su C.Ge E.F ha la precedenza sia su D.F che su C.F.

esempio finale

12.8.10.4 Invocazioni di delegati

Per una chiamata di delegato, il primary_expression del invocation_expression deve essere un valore di delegate_type. Inoltre, considerando che il delegate_type sia membro di funzione con lo stesso elenco di parametri del delegate_type, il delegate_type deve essere applicabile (§12.6.4.2) rispetto al argument_list del invocation_expression.

L'elaborazione in fase di esecuzione di una chiamata di delegato del modulo D(A), dove D è un primary_expression di un delegate_type e A è un argument_listfacoltativo , è costituito dai passaggi seguenti:

  • D viene valutato. Se questa valutazione causa un'eccezione, non vengono eseguiti altri passaggi.
  • Viene valutato l'elenco di argomenti A. Se questa valutazione causa un'eccezione, non vengono eseguiti altri passaggi.
  • Il valore di D è controllato per verificarne la validità. Se il valore di D è null, viene generata una System.NullReferenceException e non vengono eseguiti altri passaggi.
  • In caso contrario, D è un riferimento a un'istanza del delegato. Le chiamate ai membri della funzione (§12.6.6) vengono eseguite su ognuna delle entità chiamabili nell'elenco di invocazione del delegato. Per le entità chiamabili costituite da un'istanza e da un metodo di istanza, l'istanza per la chiamata è l'istanza contenuta nell'entità chiamabile.

Per informazioni dettagliate su più elenchi di chiamate senza parametri, vedere §20.6.

12.8.11 Espressione di invocazione condizionale Null

Un null_conditional_invocation_expression è sintatticamente o un null_conditional_member_access (§12.8.8) o un null_conditional_element_access (§12.8.13), dove l'ultimo dependent_access è un'espressione di invocazione (§12.8.10).

Un null_conditional_invocation_expression si verifica nel contesto di un statement_expression (§13.7), anonymous_function_body (§12.19.1) o method_body (§15.6.1).

A differenza del null_conditional_member_access o del null_conditional_element_accesssintatticamente equivalenti, un null_conditional_invocation_expression può essere classificato come niente.

null_conditional_invocation_expression
    : null_conditional_member_access null_forgiving_operator? '(' argument_list? ')'
    | null_conditional_element_access null_forgiving_operator? '(' argument_list? ')'
    ;

Il null_forgiving_operator facoltativo può essere incluso se e solo se il null_conditional_member_access o il null_conditional_element_access ha un delegate_type.

Un'espressione null_conditional_invocation_expressionE è del formato P?A; dove A è il resto del null_conditional_member_access o null_conditional_element_accessequivalente sintatticamente , A inizierà quindi con . o [. Lascia che PA significhi la concatenazione di P e A.

Quando E si verifica come statement_expression il significato di E corrisponde al significato dell'istruzione :

if ((object)P != null) PA

ad eccezione del fatto che P viene valutato una sola volta.

Quando E si verifica come "corpo_funzionale_anonimo" o "corpo_metodo", il significato di E dipende dalla sua classificazione:

  • Se E viene classificato come nulla, il suo significato è uguale al significato del blocco :

    { if ((object)P != null) PA; }
    

    ad eccezione del fatto che P viene valutato una sola volta.

  • In caso contrario, il significato di E corrisponde al significato del blocco :

    { return E; }
    

    e di conseguenza il significato di questo blocco dipende se E è sintatticamente equivalente a un null_conditional_member_access (§12.8.8) o null_conditional_element_access (§12.8.13).

12.8.12 Accesso all'elemento

12.8.12.1 Generale

Un element_access è costituito da un primary_no_array_creation_expression, seguito da un token "[", seguito da un token argument_list, seguito da un token "]". Il argument_list è costituito da uno o più argomenti , separati da virgole.

element_access
    : primary_no_array_creation_expression '[' argument_list ']'
    ;

Il argument_list di un element_access non può contenere argomenti out o ref.

Un element_access è associato dinamicamente (§12.3.3) se almeno una delle seguenti condizioni:

  • L'primary_no_array_creation_expression ha un tipo in fase di compilazione dynamic.
  • Almeno un'espressione del argument_list ha un tipo in fase di compilazione dynamic e il primary_no_array_creation_expression non ha un tipo di matrice.

In questo caso, il compilatore classifica il element_access come valore di tipo dynamic. Le regole seguenti per determinare il significato dell'elemento element_access vengono quindi applicate al momento dell'esecuzione, utilizzando il tipo di runtime anziché il tipo di compilazione delle espressioni primary_no_array_creation_expression e argument_list, che hanno il tipo determinato in fase di compilazione dynamic. Se il primary_no_array_creation_expression non ha un tipo di compilazione dynamic, l'accesso all'elemento viene sottoposto a un controllo limitato in fase di compilazione come descritto in §12.6.5.

Se il primary_no_array_creation_expression di un element_access è un valore appartenente a array_type, il element_access è un accesso all'array (§12.8.12.2). In caso contrario, il primary_no_array_creation_expression deve essere una variabile o un valore di una classe, di una struttura o di un tipo di interfaccia che abbia uno o più membri indicizzatori, in tal caso il element_access è un accesso indicizzatore (§12.8.12.3).

12.8.12.2 Accesso alla matrice

Per l'accesso a una matrice, il primary_no_array_creation_expression del element_access deve essere un valore di un array_type. Inoltre, il argument_list di accesso a una matrice non può contenere argomenti denominati. Il numero di espressioni nella argument_list deve essere uguale al rango del array_typee ogni espressione deve essere di tipo int, uint, longo ulong, o deve essere convertibile in modo implicito in uno o più di questi tipi.

Il risultato della valutazione di un accesso a una matrice è una variabile del tipo di elemento della matrice, ovvero l'elemento della matrice selezionato dai valori delle espressioni nella argument_list.

L'elaborazione in fase di esecuzione di un accesso a matrice del modulo P[A], dove P è un primary_no_array_creation_expression di un array_type e A è un argument_list, è costituito dai passaggi seguenti:

  • P viene valutato. Se questa valutazione causa un'eccezione, non vengono eseguiti altri passaggi.
  • Le espressioni di indice del argument_list vengono valutate in ordine, da sinistra a destra. Dopo la valutazione di ogni espressione di indice, viene eseguita una conversione implicita (§10.2) in uno dei tipi seguenti: int, uint, long, ulong. Viene scelto il primo tipo in questo elenco per il quale esiste una conversione implicita. Ad esempio, se l'espressione di indice è di tipo short viene eseguita una conversione implicita in int, poiché sono possibili conversioni implicite da short a int e da short a long. Se la valutazione di un'espressione di indice o la conversione implicita successiva causa un'eccezione, non vengono valutate altre espressioni di indice e non vengono eseguiti altri passaggi.
  • Il valore di P è controllato per verificarne la validità. Se il valore di P è null, viene generata una System.NullReferenceException e non vengono eseguiti altri passaggi.
  • Il valore di ogni espressione nel argument_list viene controllato rispetto ai limiti effettivi di ogni dimensione dell'istanza della matrice a cui fa riferimento P. Se uno o più valori non sono compresi nell'intervallo, viene generata una System.IndexOutOfRangeException e non vengono eseguiti altri passaggi.
  • Viene calcolata la posizione dell'elemento di matrice specificato dalle espressioni di indice e questa posizione diventa il risultato dell'accesso alla matrice.

12.8.12.3 Accesso indicizzatore

Per l'accesso a un indicizzatore, il primary_no_array_creation_expression del element_access deve essere una variabile o un valore di una classe, uno struct o un tipo di interfaccia e questo tipo implementerà uno o più indicizzatori applicabili per quanto riguarda il argument_list del element_access.

L'elaborazione in fase di associazione di un indicizzatore di accesso al modulo P[A], dove P è un primary_no_array_creation_expression di una classe, uno struct o un tipo di interfaccia Te A è un argument_list, è costituito dai passaggi seguenti:

  • Viene costruito il set di indicizzatori fornito da T. Il set è costituito da tutti gli indicizzatori dichiarati in T o in un tipo base di T che non sono dichiarazioni di override e che sono accessibili nel contesto corrente (§7.5).
  • Il set viene ridotto a quelli applicabili e non nascosti da altri indicizzatori. Le regole seguenti vengono applicate a ogni indicizzatore S.I nel set, dove S è il tipo in cui viene dichiarato l'indicizzatore I:
    • Se I non è applicabile per quanto riguarda A (§12.6.4.2), I viene rimosso dal set.
    • Se I è applicabile per quanto riguarda A (§12.6.4.2), tutti gli indicizzatori dichiarati in un tipo di base di S vengono rimossi dal set.
    • Se I è applicabile per quanto riguarda A (§12.6.4.2) e S è un tipo di classe diverso da object, tutti gli indicizzatori dichiarati in un'interfaccia vengono rimossi dal set.
  • Se il set risultante di indicizzatori candidati è vuoto, non esiste alcun indicizzatore applicabile e si verifica un errore di binding.
  • L'indicizzatore migliore del set di indicizzatori candidati viene identificato usando le regole di risoluzione dell'overload di §12.6.4. Se non è possibile identificare un singolo indicizzatore migliore, l'accesso dell'indicizzatore è ambiguo e si verifica un errore di tempo di legame.
  • Le espressioni di indice del argument_list vengono valutate in ordine, da sinistra a destra. Il risultato dell'elaborazione dell'accesso dell'indicizzatore è un'espressione classificata come accesso dell'indicizzatore. L'espressione di accesso dell'indicizzatore fa riferimento all'indicizzatore determinato nel passaggio precedente e ha un'espressione di istanza associata di P e un elenco di argomenti associato di Ae un tipo associato che è il tipo dell'indicizzatore. Se T è un tipo di classe, il tipo associato viene determinato dalla prima dichiarazione o dall'override dell'indicizzatore trovato cominciando da T e continuando la ricerca attraverso le sue classi di base.

A seconda del contesto in cui viene usato, l'accesso di un indicizzatore provoca la chiamata del metodo di accesso get o del metodo di accesso set dell'indicizzatore. Se l'accesso all'indicizzatore è la destinazione di un'assegnazione, l'accessor set viene richiamato per assegnare un nuovo valore (§12.21.2). In tutti gli altri casi, l'accessor get viene invocato per ottenere il valore corrente (§12.2.2).

12.8.13 Accesso condizionale Null

Un null_conditional_element_access è costituito da un primary_no_array_creation_expression seguito dai due token "?" e "[", seguito da un argument_list, seguito da un token "]", seguito da zero o più dependent_access, ognuno dei quali può essere preceduto da un null_forgiving_operator.

null_conditional_element_access
    : primary_no_array_creation_expression '?' '[' argument_list ']'
      (null_forgiving_operator? dependent_access)*
    ;

Un null_conditional_element_access è una versione condizionale di element_access (§12.8.12) e rappresenta un errore di tempo di associazione se il tipo di risultato è void. Per un'espressione condizionale nulla in cui il tipo di risultato può essere void, consultare (§12.8.11).

Un'espressione null_conditional_element_accessE è del formato P?[A]B; dove B sono i dependent_accesses, se presenti. Il significato di E è determinato nel modo seguente:

  • Se il tipo di P è un tipo di valore annullabile:

    Sia T il tipo dell'espressione P.Value[A]B.

    • Se T è un parametro di tipo di cui non si sa se sia un tipo di riferimento o un tipo di valore non annullabile, si verifica un errore in fase di compilazione.

    • Se T è un tipo valore non nullable, il tipo di E è T?e il significato di E è lo stesso del significato di:

      ((object)P == null) ? (T?)null : P.Value[A]B
      

      Ad eccezione del fatto che P viene valutato una sola volta.

    • In caso contrario, il tipo di E è Te il significato di E corrisponde al significato di:

      ((object)P == null) ? null : P.Value[A]B
      

      Ad eccezione del fatto che P viene valutato una sola volta.

  • Altrimenti:

    Sia T il tipo dell'espressione P[A]B.

    • Se T è un parametro di tipo di cui non si sa se sia un tipo di riferimento o un tipo di valore non annullabile, si verifica un errore in fase di compilazione.

    • Se T è un tipo valore non nullable, il tipo di E è T?e il significato di E è lo stesso del significato di:

      ((object)P == null) ? (T?)null : P[A]B
      

      Ad eccezione del fatto che P viene valutato una sola volta.

    • In caso contrario, il tipo di E è Te il significato di E corrisponde al significato di:

      ((object)P == null) ? null : P[A]B
      

      Ad eccezione del fatto che P viene valutato una sola volta.

Nota: in un'espressione del formato:

P?[A₀]?[A₁]

se P restituisce null non vengono valutati né A₀A₁. Lo stesso vale se un'espressione è una sequenza di operazioni di null_conditional_element_access o null_conditional_member_access§12.8.8.

nota finale

12.8.14 Questo accesso

Un this_access è costituito dalla parola chiave this.

this_access
    : 'this'
    ;

Un this_access è consentito solo nel blocco di un costruttore di istanza, un metodo di istanza, una funzione di accesso all'istanza (§12.2.1) o un finalizzatore. Ha uno dei significati seguenti:

  • Quando this viene usato in un primary_expression all'interno di un costruttore di istanza di una classe, viene classificato come valore. Il tipo di valore è il tipo di istanza (§15.3.2) della classe all'interno della quale si verifica l'utilizzo e il valore è un riferimento all'oggetto costruito.
  • Quando this viene usato in un primary_expression all'interno di un metodo o accessore di istanza di una classe, viene classificato come valore. Il tipo del valore è il tipo di istanza (§15.3.2) della classe all'interno della quale si verifica l'utilizzo e il valore è un riferimento all'oggetto per cui è stato richiamato il metodo o la funzione di accesso.
  • Quando this viene usato in un primary_expression all'interno di un costruttore di istanza di uno struct, viene classificato come variabile. Il tipo della variabile è il tipo di istanza (§15.3.2) dello struct all'interno del quale si verifica l'utilizzo e la variabile rappresenta lo struct da costruire.
    • Se la dichiarazione del costruttore non dispone di inizializzatore del costruttore, la variabile this si comporta esattamente come un parametro di output del tipo di struct. In particolare, ciò significa che la variabile deve essere sicuramente assegnata in ogni percorso di esecuzione del costruttore di istanza.
    • In caso contrario, la variabile this si comporta esattamente come un parametro ref del tipo struct. In particolare, ciò significa che la variabile viene considerata inizialmente assegnata.
  • Quando this viene usato in un'espressione primaria all'interno di un metodo di istanza o di un accessor di istanza di una struct, viene considerato come variabile. Il tipo della variabile è il tipo di istanza (§15.3.2) dello struct in cui si verifica l'utilizzo.
    • Se il metodo o la funzione di accesso non è un iteratore (§15.14) o una funzione asincrona (§15.15), la variabile this rappresenta lo struct per cui è stato richiamato il metodo o la funzione di accesso.
      • Se il struct è un readonly struct, la variabile this si comporta esattamente come un parametro di input del tipo struct.
      • In caso contrario, la variabile this si comporta esattamente come un parametro ref del tipo struct
    • Se il metodo o la funzione di accesso è un iteratore o una funzione asincrona, la variabile this rappresenta una copia della struttura per cui è stato richiamato il metodo o la funzione di accesso e si comporta esattamente come un parametro valore di tipo struttura.

L'uso di this in un primary_expression in un contesto diverso da quelli elencati in precedenza è un errore in fase di compilazione. In particolare, non è possibile fare riferimento a this in un metodo statico, un accessore di proprietà statico, o in un variable_initializer di una dichiarazione di campo.

12.8.15 Accesso di base

Un base_access è costituito dalla parola chiave base seguita da un token “.” e un identificatore e un type_argument_list facoltativo o un argument_list racchiuso tra parentesi quadre:

base_access
    : 'base' '.' identifier type_argument_list?
    | 'base' '[' argument_list ']'
    ;

Un base_access viene utilizzato per accedere ai membri della classe base che sono nascosti da membri con nomi simili nella classe o nella struttura corrente. Un base_access è consentito solo nel corpo di un costruttore di istanza, un metodo di istanza, una funzione di accesso all'istanza (§12.2.1) o un finalizzatore. Quando base.I si verifica in una classe o in una struttura, posso denoterò un membro della classe base di tale classe o struttura. Analogamente, quando base[E] si verifica in una classe, un indicizzatore applicabile deve esistere nella classe di base.

In fase di associazione, le espressioni base_access della forma base.I e base[E] vengono valutate esattamente come se fossero scritte ((B)this).I e ((B)this)[E], dove B è la classe base della classe o dello struct in cui si verifica il costrutto. Pertanto, base.I e base[E] corrispondono a this.I e this[E], ad eccezione di this viene considerato come un'istanza della classe base.

Quando un base_access fa riferimento a un membro di funzione virtuale (metodo, proprietà o indicizzatore), viene modificata la determinazione del membro della funzione da richiamare in fase di esecuzione (§12.6.6). Il membro della funzione richiamato è identificato trovando l'implementazione più derivata (§15.6.4) del membro della funzione rispetto a B (invece che rispetto al tipo di runtime di this, come sarebbe usuale in un accesso non di base). Pertanto, all'interno di un override di un membro di funzione virtuale, è possibile utilizzare un base_access per invocare l'implementazione ereditata del membro di funzione. Se la funzione a cui fa riferimento un base_access è astratta, si verifica un errore di assegnazione temporale.

Nota: a differenza di this, base non è un'espressione in sé. Si tratta di una parola chiave usata solo nel contesto di un base_access o di un constructor_initializer (§15.11.2). nota finale

12.8.16 Operatori di incremento e decremento postfissi

post_increment_expression
    : primary_expression '++'
    ;

post_decrement_expression
    : primary_expression '--'
    ;

L'operando di un'operazione di incremento o decremento suffisso deve essere un'espressione classificata come variabile, accesso alle proprietà o accesso a un indicizzatore. Il risultato dell'operazione è un valore dello stesso tipo dell'operando.

Se l'primary_expression ha il tipo determinato in fase di compilazione dynamic, l'operatore viene associato dinamicamente (§12.3.3), il post_increment_expression o post_decrement_expression ha il tipo determinato in fase di compilazione dynamic e le seguenti regole sono applicate durante l'esecuzione utilizzando il tipo di esecuzione dell'primary_expression.

Se l'operando di un'operazione di incremento o decremento suffisso è una proprietà o un indicizzatore, la proprietà o l'indicizzatore devono avere sia un metodo di accesso get che un metodo di accesso set. In caso contrario, si verifica un errore di tempo di legame.

La risoluzione dell'overload dell'operatore unario (§12.4.4) viene applicata per selezionare un'implementazione specifica dell'operatore. Esistono operatori predefiniti ++ e -- per i tipi seguenti: sbyte, byte, short, ushort, int, uint, long, ulong, char, float, double, decimale qualsiasi tipo di enumerazione. Gli operatori ++ predefiniti restituiscono il valore prodotto aggiungendo 1 all'operando e gli operatori -- predefiniti restituiscono il valore prodotto sottraendo 1 dall'operando. In un contesto controllato, se il risultato di questa addizione o sottrazione non è compreso nell'intervallo del tipo di risultato e il tipo di risultato è un tipo integrale o un tipo enum, viene generata una System.OverflowException.

È prevista una conversione implicita dal tipo restituito dell'operatore unario selezionato al tipo di primary_expression, altrimenti si verifica un errore in fase di compilazione.

L'elaborazione in fase di esecuzione di un'operazione di incremento o decremento postfisso della forma x++ o x-- si compone dei seguenti passaggi:

  • Se x è classificato come variabile:
    • x viene valutato per produrre la variabile.
    • Il valore di x viene salvato.
    • Il valore salvato di x viene convertito nel tipo operando dell'operatore selezionato e l'operatore viene richiamato con questo valore come argomento.
    • Il valore restituito dall'operatore viene convertito nel tipo di x e archiviato nella posizione specificata dalla valutazione precedente di x.
    • Il valore salvato di x diventa il risultato dell'operazione.
  • Se x è classificato come accesso a proprietà o indicizzatore:
    • L'espressione di istanza (se x non è static) e l'elenco di argomenti (se x è un accesso indicizzatore) associato a x vengono valutati e i risultati vengono usati nelle chiamate delle funzioni di accesso get e set successive.
    • Viene richiamata la funzione di accesso get di x e il valore restituito viene salvato.
    • Il valore salvato di x viene convertito nel tipo operando dell'operatore selezionato e l'operatore viene richiamato con questo valore come argomento.
    • Il valore restituito dall'operatore viene convertito nel tipo di x e il metodo accessor set di x viene richiamato con questo valore come argomento.
    • Il valore salvato di x diventa il risultato dell'operazione.

Gli operatori ++ e -- supportano anche la notazione del prefisso (§12.9.6). Il risultato di x++ o x-- è il valore di xprima dell'operazione, mentre il risultato di ++x o --x è il valore di xdopo l'operazione. In entrambi i casi, x ha lo stesso valore dopo l'operazione.

È possibile richiamare un'implementazione di operatori ++ o -- utilizzando la notazione prefissa o postfissa. Non è possibile avere implementazioni di operatori separate per le due notazioni.

12.8.17 Nuovo operatore

12.8.17.1 Generale

L'operatore new viene usato per creare nuove istanze di tipi.

Esistono tre forme di nuove espressioni:

  • Le espressioni di creazione di oggetti e le espressioni di creazione di oggetti anonimi vengono usate per creare nuove istanze di tipi di classe e tipi valore.
  • Le espressioni di creazione di matrici vengono usate per creare nuove istanze di tipi di matrice.
  • Le espressioni di creazione dei delegati vengono usate per ottenere istanze di tipi di delegati.

L'operatore new implica la creazione di un'istanza di un tipo, ma non implica necessariamente l'allocazione della memoria. In particolare, le istanze dei tipi valore non richiedono memoria aggiuntiva oltre le variabili in cui risiedono e non si verificano allocazioni quando new viene usata per creare istanze di tipi valore.

Nota: le espressioni di creazione di delegati non creano sempre nuove istanze. Quando l'espressione viene elaborata nello stesso modo della conversione di un gruppo di metodi (§10.8) o di una conversione di funzione anonima (§10.7) ciò può comportare il riutilizzo di un'istanza del delegato esistente. nota finale

12.8.17.2 Espressioni di creazione di oggetti

Un object_creation_expression viene usato per creare una nuova istanza di un class_type o un value_type.

object_creation_expression
    : 'new' type '(' argument_list? ')' object_or_collection_initializer?
    | 'new' type object_or_collection_initializer
    ;

object_or_collection_initializer
    : object_initializer
    | collection_initializer
    ;

Il tipo di un object_creation_expression deve essere un class_type, un value_typeo un type_parameter. Il tipo non può essere un tuple_type o un class_typestatico o astratto.

Il argument_list facoltativo (§12.6.2) è consentito solo se il tipo è un class_type o un struct_type.

Un'espressione di creazione di oggetti può omettere l'elenco di argomenti del costruttore e racchiudere le parentesi, purché includa un inizializzatore di oggetto o un inizializzatore di raccolta. Omettere l'elenco di argomenti del costruttore e racchiudere parentesi equivale a specificare un elenco di argomenti vuoto.

L'elaborazione di un'espressione di creazione di oggetti che include un inizializzatore di oggetto o di un inizializzatore di raccolta consiste nell'elaborare prima il costruttore dell'istanza e quindi elaborare le inizializzazioni membro o elemento specificate dall'inizializzatore di oggetto (§12.8.17.17.3) o l'inizializzatore di raccolta (§12.8.17.4).

Se uno degli argomenti nel argument_list facoltativo ha il tipo in fase di compilazione dynamic, l' object_creation_expression viene legato dinamicamente (§12.3.3) e le regole seguenti vengono applicate in fase di esecuzione utilizzando il tipo di runtime degli argomenti del argument_list che hanno il tipo in fase di compilazione dynamic. Tuttavia, la creazione dell'oggetto viene sottoposta a un controllo in fase di compilazione limitato, come descritto in §12.6.5.

L'elaborazione in fase di associazione di un object_creation_expression della forma new T(A), dove T è un class_typeo un value_typee A è un argument_listfacoltativo, consiste nei seguenti passaggi:

  • Se T è un value_type e A non è presente:
    • Il object_creation_expression è una chiamata del costruttore predefinita. Il risultato del object_creation_expression è un valore di tipo T, ovvero il valore predefinito per T come definito in §8.3.3.
  • In caso contrario, se T è un type_parameter e A non è presente:
    • Se non è stato specificato alcun vincolo di tipo valore o vincolo di costruttore (§15.2.5) per T, si verifica un errore di tempo di vincolo.
    • Il risultato dell'object_creation_expression è un valore del tipo di runtime a cui è stato associato il parametro di tipo, ovvero il risultato della chiamata del costruttore predefinito di tale tipo. Il tipo di runtime può essere un tipo di riferimento o un tipo di valore.
  • In caso contrario, se T è un class_type o un struct_type:
    • Se T è un class_typeastratto o statico, si verifica un errore in fase di compilazione.
    • Il costruttore di istanza da richiamare viene determinato usando le regole di risoluzione dell'overload di §12.6.4. Il set di costruttori di istanze candidate è costituito da tutti i costruttori di istanza accessibili dichiarati in T, applicabili rispetto a A (§12.6.4.2). Se il set di costruttori di istanze candidate è vuoto, o se non è possibile identificare un singolo miglior costruttore di istanza, si verifica un errore di associazione in fase di legatura.
    • Il risultato della object_creation_expression è un valore di tipo T, ovvero il valore generato richiamando il costruttore dell'istanza determinato nel passaggio precedente.
    • In caso contrario, il object_creation_expression non è valido e si verifica un errore di binding.

Anche se il object_creation_expression è associato dinamicamente, il tipo in fase di compilazione è ancora T.

L'elaborazione in fase di esecuzione di un object_creation_expression del modulo nuovo T(A), dove T è class_type o un struct_type e A è un argument_listfacoltativo , è costituito dai passaggi seguenti:

  • Se T è un class_type:
    • Viene allocata una nuova istanza della classe T. Se non è disponibile memoria sufficiente per allocare la nuova istanza, viene generata una System.OutOfMemoryException e non vengono eseguiti altri passaggi.
    • Tutti i campi della nuova istanza vengono inizializzati sui valori predefiniti (§9.3).
    • Il costruttore dell'istanza viene richiamato in base alle regole di chiamata del membro della funzione (§12.6.6). Un riferimento all'istanza appena allocata viene passato automaticamente al costruttore dell'istanza e l'istanza può essere acceduta automaticamente dall'interno di tale costruttore.
  • Se T è un struct_type:
    • Un'istanza di tipo T viene creata allocando una variabile locale temporanea. Poiché è necessario un costruttore di istanza di un struct_type per assegnare sicuramente un valore a ogni campo dell'istanza da creare, non è necessaria alcuna inizializzazione della variabile temporanea.
    • Il costruttore dell'istanza viene richiamato in base alle regole di chiamata del membro della funzione (§12.6.6). Un riferimento all'istanza appena allocata viene passato automaticamente al costruttore dell'istanza e l'istanza può essere acceduta automaticamente dall'interno di tale costruttore.

12.8.17.3 Inizializzatori di oggetti

Un inizializzatore di oggetti specifica i valori per zero o più campi, proprietà o elementi indicizzati di un oggetto.

object_initializer
    : '{' member_initializer_list? '}'
    | '{' member_initializer_list ',' '}'
    ;

member_initializer_list
    : member_initializer (',' member_initializer)*
    ;

member_initializer
    : initializer_target '=' initializer_value
    ;

initializer_target
    : identifier
    | '[' argument_list ']'
    ;

initializer_value
    : expression
    | object_or_collection_initializer
    ;

Un inizializzatore di oggetti è costituito da una sequenza di inizializzatori di membri, racchiusi da { e } token e separati da virgole. Ogni member_initializer deve designare una destinazione per l'inizializzazione. Un identificatore denomina un campo o una proprietà accessibile dell'oggetto da inizializzare, mentre un argument_list racchiuso tra parentesi quadre specifica gli argomenti per un indicizzatore accessibile sull'oggetto da inizializzare. È un errore che un inizializzatore di oggetto includa più di un inizializzatore per un membro relativo allo stesso campo o proprietà.

Nota: mentre un inizializzatore di oggetto non è autorizzato a impostare più volte lo stesso campo o la stessa proprietà, non esistono restrizioni di questo tipo per gli indicizzatori. Un inizializzatore di oggetti può contenere più destinazioni di inizializzatore che fanno riferimento agli indicizzatori e può anche usare gli stessi argomenti dell'indicizzatore più volte. nota finale

Ogni initializer_target è seguito da un segno di uguale e da un'espressione, da un inizializzatore di oggetto o da un inizializzatore di raccolta. Non è possibile che le espressioni all'interno dell'inizializzatore dell'oggetto facciano riferimento all'oggetto appena creato che sta inizializzando.

Un inizializzatore di membro che specifica un'espressione dopo il segno di uguale viene elaborato nello stesso modo di un'assegnazione (§12.21.2) al target.

Un inizializzatore di un membro che specifica un inizializzatore di oggetto dopo il segno di uguale è un inizializzatore di oggetto annidato , cioè un'inizializzazione di un oggetto incorporato. Anziché assegnare un nuovo valore al campo o alla proprietà, le assegnazioni nell'inizializzatore dell'oggetto annidato vengono considerate assegnazioni ai membri del campo o della proprietà. Gli inizializzatori di oggetti annidati non possono essere applicati alle proprietà con un tipo di valore o ai campi di sola lettura con un tipo di valore.

Un inizializzatore di membro che specifica un inizializzatore di raccolta dopo il segno di uguale è un'inizializzazione di una raccolta annidata. Anziché assegnare una nuova raccolta al campo di destinazione, alla proprietà o all'indicizzatore, gli elementi specificati nell'inizializzatore vengono aggiunti alla raccolta a cui fa riferimento la destinazione. La destinazione deve essere di un tipo di raccolta che soddisfi i requisiti specificati in §12.8.17.4.

Quando una destinazione di inizializzatore fa riferimento a un indicizzatore, gli argomenti dell'indicizzatore devono essere sempre valutati esattamente una volta. Pertanto, anche se gli argomenti finiscono per non essere mai usati (ad esempio, a causa di un inizializzatore annidato vuoto), vengono valutati per i loro effetti collaterali.

esempio: la classe seguente rappresenta un punto con due coordinate:

public class Point
{
    public int X { get; set; }
    public int Y { get; set; }
}

È possibile creare e inizializzare un'istanza di Point come indicato di seguito:

Point a = new Point { X = 0, Y = 1 };

Questo ha lo stesso effetto di

Point __a = new Point();
__a.X = 0;
__a.Y = 1;
Point a = __a;

dove __a è una variabile temporanea altrimenti invisibile e inaccessibile.

La classe seguente mostra un rettangolo creato da due punti e la creazione e l'inizializzazione di un'istanza di Rectangle:

public class Rectangle
{
    public Point P1 { get; set; }
    public Point P2 { get; set; }
}

È possibile creare e inizializzare un'istanza di Rectangle come indicato di seguito:

Rectangle r = new Rectangle
{
    P1 = new Point { X = 0, Y = 1 },
    P2 = new Point { X = 2, Y = 3 }
};

Questo ha lo stesso effetto di

Rectangle __r = new Rectangle();
Point __p1 = new Point();
__p1.X = 0;
__p1.Y = 1;
__r.P1 = __p1;
Point __p2 = new Point();
__p2.X = 2;
__p2.Y = 3;
__r.P2 = __p2;
Rectangle r = __r;

dove __r, __p1 e __p2 sono variabili temporanee altrimenti invisibili e inaccessibili.

Se il costruttore di Rectanglealloca le due istanze incorporate di Point, esse possono essere utilizzate per inizializzare le istanze incorporate di Point anziché assegnare nuove istanze.

public class Rectangle
{
    public Point P1 { get; } = new Point();
    public Point P2 { get; } = new Point();
}

Il costrutto seguente può essere usato per inizializzare le istanze di Point incorporate anziché assegnare nuove istanze:

Rectangle r = new Rectangle
{
    P1 = { X = 0, Y = 1 },
    P2 = { X = 2, Y = 3 }
};

Questo ha lo stesso effetto di

Rectangle __r = new Rectangle();
__r.P1.X = 0;
__r.P1.Y = 1;
__r.P2.X = 2;
__r.P2.Y = 3;
Rectangle r = __r;

esempio finale

12.8.17.4 Inizializzatori di raccolta

Un inizializzatore di raccolta specifica gli elementi di una raccolta.

collection_initializer
    : '{' element_initializer_list '}'
    | '{' element_initializer_list ',' '}'
    ;

element_initializer_list
    : element_initializer (',' element_initializer)*
    ;

element_initializer
    : non_assignment_expression
    | '{' expression_list '}'
    ;

expression_list
    : expression
    | expression_list ',' expression
    ;

Un inizializzatore di raccolta è costituito da una sequenza di inizializzatori di elementi, racchiusi tra { e } token e separati da virgole. Ogni inizializzatore di elemento specifica un elemento da aggiungere all'oggetto raccolta da inizializzare ed è costituito da un elenco di espressioni racchiuse da { e } token e separati da virgole. Un inizializzatore di elemento a espressione singola può essere scritto senza parentesi graffe, ma non può essere un'espressione di assegnazione, per evitare ambiguità con gli inizializzatori di membri. La produzione non_assignment_expression è definita in §12.22.

esempio: di seguito è riportato un esempio di espressione di creazione di oggetti che include un inizializzatore di raccolta:

List<int> digits = new List<int> { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };

esempio finale

L'oggetto raccolta a cui viene applicato un inizializzatore di raccolta deve essere di un tipo che implementa System.Collections.IEnumerable, altrimenti si verifica un errore di compilazione. Per ogni elemento specificato in ordine da sinistra a destra, viene applicata la ricerca normale dei membri per trovare un membro denominato Add. Se il risultato della ricerca del membro non è un gruppo di metodi, si verifica un errore in fase di compilazione. Altrimenti, la risoluzione dell'overload viene applicata utilizzando l'elenco di espressioni dell'inizializzatore di elementi come lista di argomenti, quindi l'inizializzatore di raccolta invoca il metodo risultante. Pertanto, l'oggetto raccolta deve contenere un'istanza applicabile o un metodo di estensione con il nome Add per ogni inizializzatore di elemento.

esempio: di seguito viene illustrata una classe che rappresenta un contatto con un nome e un elenco di numeri di telefono e la creazione e l'inizializzazione di un List<Contact>:

public class Contact
{
    public string Name { get; set; }
    public List<string> PhoneNumbers { get; } = new List<string>();
}

class A
{
    static void M()
    {
        var contacts = new List<Contact>
        {
            new Contact
            {
                Name = "Chris Smith",
                PhoneNumbers = { "206-555-0101", "425-882-8080" }
            },
            new Contact
            {
                Name = "Bob Harris",
                PhoneNumbers = { "650-555-0199" }
            }
        };
    }
}

che ha lo stesso effetto di

var __clist = new List<Contact>();
Contact __c1 = new Contact();
__c1.Name = "Chris Smith";
__c1.PhoneNumbers.Add("206-555-0101");
__c1.PhoneNumbers.Add("425-882-8080");
__clist.Add(__c1);
Contact __c2 = new Contact();
__c2.Name = "Bob Harris";
__c2.PhoneNumbers.Add("650-555-0199");
__clist.Add(__c2);
var contacts = __clist;

dove __clist, __c1 e __c2 sono variabili temporanee altrimenti invisibili e inaccessibili.

esempio finale

12.8.17.5 Espressioni di creazione di matrici

Un array_creation_expression viene usato per creare una nuova istanza di un array_type.

array_creation_expression
    : 'new' non_array_type '[' expression_list ']' rank_specifier*
      array_initializer?
    | 'new' array_type array_initializer
    | 'new' rank_specifier array_initializer
    ;

Un'espressione di creazione della matrice del primo modulo alloca un'istanza di matrice del tipo risultante dall'eliminazione di ognuna delle singole espressioni dall'elenco di espressioni.

esempio: l'espressione di creazione della matrice new int[10,20] produce un'istanza di matrice di tipo int[,]e l'espressione di creazione della matrice nuova int[10][,] produce un'istanza di matrice di tipo int[][,]. esempio finale

Ogni espressione nell'elenco di espressioni deve essere di tipo int, uint, longo ulongo convertibile in modo implicito in uno o più di questi tipi. Il valore di ogni espressione determina la lunghezza della dimensione corrispondente nella nuova istanza dell'array appena allocata. Poiché la lunghezza di una dimensione di matrice deve essere non negativa, si tratta di un errore in fase di compilazione avere un'espressione costante con un valore negativo, nella lista delle espressioni.

Ad eccezione di un contesto non sicuro (§23.2), il layout delle matrici non è specificato.

Se un'espressione di creazione della matrice del primo modulo include un inizializzatore di matrice, ogni espressione nell'elenco di espressioni sarà una costante e le lunghezze di rango e dimensione specificate dall'elenco di espressioni corrisponderanno a quelle dell'inizializzatore di matrice.

In un'espressione di creazione della matrice del secondo o del terzo formato, il rango del tipo di matrice o dell'identificatore di rango specificato deve corrispondere a quello dell'inizializzatore di matrice. Le singole lunghezze delle dimensioni vengono dedotte dal numero di elementi in ognuno dei livelli di annidamento corrispondenti dell'inizializzatore di matrice. Pertanto, nella dichiarazione seguente, l'espressione dell'inizializzatore

var a = new int[,] {{0, 1}, {2, 3}, {4, 5}};

corrisponde esattamente a

var a = new int[3, 2] {{0, 1}, {2, 3}, {4, 5}};

Un'espressione di creazione di matrici di terza forma si riferisce a un'espressione di creazione di matrici implicitamente tipizzata . È simile al secondo modulo, ad eccezione del fatto che il tipo di elemento della matrice non viene specificato in modo esplicito, ma determinato come il tipo comune migliore (§12.6.3.15) del set di espressioni nell'inizializzatore di matrice. Per una matrice multidimensionale, ovvero una in cui il rank_specifier contiene almeno una virgola, questo set include tutte le espressioni trovate nei array_initializerannidati.

Gli inizializzatori di matrice sono descritti ulteriormente in §17.7.

Il risultato della valutazione di un'espressione di creazione di matrice viene classificato come valore, ovvero un riferimento all'istanza di matrice appena allocata. L'elaborazione in fase di esecuzione di un'espressione di creazione della matrice è costituita dai passaggi seguenti:

  • Le espressioni di lunghezza della dimensione del expression_list vengono valutate in ordine, da sinistra a destra. Dopo la valutazione di ogni espressione, viene eseguita una conversione implicita (§10.2) in uno dei tipi seguenti: int, uint, long, ulong. Viene scelto il primo tipo in questo elenco per il quale esiste una conversione implicita. Se la valutazione di un'espressione o della conversione implicita successiva causa un'eccezione, non vengono valutate altre espressioni e non vengono eseguiti altri passaggi.
  • I valori calcolati per le lunghezze delle dimensioni vengono convalidati, come indicato di seguito: se uno o più valori sono minori di zero, viene generata una System.OverflowException e non vengono eseguiti altri passaggi.
  • Viene allocata un'istanza di matrice con le lunghezze della dimensione specificata. Se non è disponibile memoria sufficiente per allocare la nuova istanza, viene generata una System.OutOfMemoryException e non vengono eseguiti altri passaggi.
  • Tutti gli elementi della nuova istanza della matrice vengono inizializzati sui valori predefiniti (§9.3).
  • Se l'espressione di creazione della matrice contiene un inizializzatore di matrice, ogni espressione nell'inizializzatore di matrice viene valutata e assegnata all'elemento della matrice corrispondente. Le valutazioni e le assegnazioni vengono eseguite nell'ordine in cui le espressioni vengono scritte nell'inizializzatore di matrice. In altre parole, gli elementi vengono inizializzati in ordine di indice crescente, con la dimensione più a destra che aumenta per prima. Se la valutazione di una determinata espressione o dell'assegnazione successiva all'elemento della matrice corrispondente causa un'eccezione, non vengono inizializzati altri elementi e gli elementi rimanenti avranno quindi i valori predefiniti.

Un'espressione di creazione di matrice consente la creazione di un'istanza di una matrice con elementi di un tipo di matrice, ma gli elementi di tale matrice devono essere inizializzati manualmente.

esempio: dichiarazione

int[][] a = new int[100][];

crea una matrice unidimensionale con 100 elementi di tipo int[]. Il valore iniziale di ogni elemento è null. Non è possibile che la stessa espressione di creazione di matrici crei anche un'istanziazione delle sottomatrici, e l'istruzione

int[][] a = new int[100][5]; // Error

genera un errore in fase di compilazione. La creazione di istanze delle sottomatrici può invece essere eseguita manualmente, come in

int[][] a = new int[100][];
for (int i = 0; i < 100; i++)
{
    a[i] = new int[5];
}

esempio finale

Nota: quando una matrice di matrici ha una forma "rettangolare", ovvero quando le sottomatrici sono tutte della stessa lunghezza, è più efficiente usare una matrice multidimensionale. Nell'esempio precedente, la creazione di un'istanza della matrice di matrici crea 101 oggetti, una matrice esterna e 100 sottomatrici. Al contrario,

int[,] a = new int[100, 5];

crea solo un singolo oggetto, una matrice bidimensionale ed esegue l'allocazione in una singola istruzione.

nota finale

esempio: di seguito sono riportati esempi di espressioni di creazione di matrici tipizzate in modo implicito:

var a = new[] { 1, 10, 100, 1000 };                     // int[]
var b = new[] { 1, 1.5, 2, 2.5 };                       // double[]
var c = new[,] { { "hello", null }, { "world", "!" } }; // string[,]
var d = new[] { 1, "one", 2, "two" };                   // Error

L'ultima espressione causa un errore in fase di compilazione perché né intstring è convertibile in modo implicito nell'altro e pertanto non esiste un tipo comune migliore. In questo caso è necessario usare un'espressione di creazione di matrice tipizzata in modo esplicito, ad esempio specificando il tipo da object[]. In alternativa, è possibile eseguire il cast di uno degli elementi a un tipo di base comune, che di conseguenza diventerà il tipo di elemento dedotto.

esempio finale

Le espressioni di creazione di matrici tipizzate in modo implicito possono essere combinate con inizializzatori di oggetti anonimi (§12.8.17.7) per creare strutture di dati tipizzate in modo anonimo.

esempio:

var contacts = new[]
{
    new
    {
        Name = "Chris Smith",
        PhoneNumbers = new[] { "206-555-0101", "425-882-8080" }
    },
    new 
    {
        Name = "Bob Harris",
       PhoneNumbers = new[] { "650-555-0199" }
    }
};

esempio finale

12.8.17.6 Espressioni di creazione delegati

Un delegate_creation_expression viene usato per ottenere un'istanza di un delegate_type.

delegate_creation_expression
    : 'new' delegate_type '(' expression ')'
    ;

L'argomento di un'espressione di creazione di delegato deve essere un gruppo di metodi, una funzione anonima o un valore del tipo di tempo di compilazione dynamic o un tipo di delegato. Se l'argomento è un insieme di metodi, identifica il metodo e, per un metodo di istanza, l'oggetto per il quale creare un delegato. Se l'argomento è una funzione anonima, definisce direttamente i parametri e il corpo del metodo della destinazione del delegato. Se l'argomento è un valore, identifica un'istanza del delegato da cui creare una copia.

Se l'espressione ha il tipo in fase di compilazione dynamic, il delegate_creation_expression è associato dinamicamente (§12.8.17.6) e le regole seguenti vengono applicate in fase di esecuzione usando il tipo di runtime dell'espressione . In caso contrario, le regole vengono applicate in fase di compilazione.

L'elaborazione in fase di associazione di un delegate_creation_expression del modulo nuovo D(E), dove D è un delegate_type e E è un'espressione , è costituita dai passaggi seguenti:

  • Se E è un gruppo di metodi, l'espressione di creazione del delegato viene elaborata nello stesso modo della conversione di un gruppo di metodi (§10,8) da E a D.

  • Se E è una funzione anonima, l'espressione di creazione del delegato viene elaborata allo stesso modo di una conversione di funzione anonima (§10,7) da E a D.

  • Se E è un valore, E deve essere compatibile (§20.2) con D, e il risultato è un riferimento a un delegato appena creato con un elenco di chiamate a singola voce che richiama E.

L'elaborazione in fase di esecuzione di una delegate_creation_expression del modulo nuovo D(E), dove D è un delegate_type e E è un'espressione , è costituita dai passaggi seguenti:

  • Se E è un gruppo di metodi, l'espressione di creazione del delegato viene valutata come conversione del gruppo di metodi (§10.8) da E a D.
  • Se E è una funzione anonima, la creazione del delegato viene valutata come conversione anonima da E a D (§10,7).
  • Se E è un valore di un delegate_type:
    • E viene valutato. Se questa valutazione causa un'eccezione, non vengono eseguiti altri passaggi.
    • Se il valore di E è null, viene generata una System.NullReferenceException e non vengono eseguiti altri passaggi.
    • Viene allocata una nuova istanza del tipo delegato D. Se non è disponibile memoria sufficiente per allocare la nuova istanza, viene generata una System.OutOfMemoryException e non vengono eseguiti altri passaggi.
    • La nuova istanza del delegato viene inizializzata con un elenco chiamate a voce singola che richiama E.

L'elenco delle chiamate di un delegato viene determinato quando un delegato viene istanziato e rimane costante per l'intera durata del delegato. In altre parole, non è possibile modificare le entità chiamabili di destinazione di un delegato dopo la creazione.

Nota: Tenere presente che quando due delegati vengono combinati o uno viene rimosso da un altro, risulta un nuovo delegato; nessun delegato esistente ha modificato il contenuto. nota finale

Non è possibile creare un delegato che fa riferimento a una proprietà, un indicizzatore, un operatore definito dall'utente, un costruttore di istanza, un finalizzatore o un costruttore statico.

Esempio: come descritto in precedenza, quando un delegato viene creato da un gruppo di metodi, l'elenco dei parametri e il tipo di ritorno del delegato determinano quale dei metodi sovraccarichi selezionare. Nell'esempio

delegate double DoubleFunc(double x);

class A
{
    DoubleFunc f = new DoubleFunc(Square);

    static float Square(float x) => x * x;
    static double Square(double x) => x * x;
}

il campo A.f viene inizializzato con un delegato che fa riferimento al secondo metodo Square perché tale metodo corrisponde esattamente all'elenco di parametri e al tipo restituito di DoubleFunc. Se il secondo metodo Square non fosse presente, si sarebbe verificato un errore in fase di compilazione.

esempio finale

12.8.17.7 Espressioni di creazione di oggetti anonimi

Un anonymous_object_creation_expression è utilizzato per creare un oggetto di tipo anonimo.

anonymous_object_creation_expression
    : 'new' anonymous_object_initializer
    ;

anonymous_object_initializer
    : '{' member_declarator_list? '}'
    | '{' member_declarator_list ',' '}'
    ;

member_declarator_list
    : member_declarator (',' member_declarator)*
    ;

member_declarator
    : simple_name
    | member_access
    | null_conditional_projection_initializer
    | base_access
    | identifier '=' expression
    ;

Un inizializzatore di oggetto anonimo dichiara un tipo anonimo e restituisce un'istanza di tale tipo. Un tipo anonimo è un tipo di classe senza nome che eredita direttamente da object. I membri di un tipo anonimo sono una sequenza di proprietà di sola lettura dedotte dall'inizializzatore di oggetto anonimo utilizzato per creare un'istanza del tipo. In particolare, un inizializzatore di oggetto anonimo del modulo

new { p₁=e₁,p₂=e₂, ... pv=ev}

dichiara un tipo anonimo del modulo

class __Anonymous1
{
    private readonly «T1» «f1»;
    private readonly «T2» «f2»;
    ...
    private readonly «Tn» «fn»;

    public __Anonymous1(«T1» «a1», «T2» «a2»,..., «Tn» «an»)
    {
        «f1» = «a1»;
        «f2» = «a2»;
        ...
        «fn» = «an»;
    }

    public «T1» «p1» { get { return «f1»; } }
    public «T2» «p2» { get { return «f2»; } }
    ...
    public «Tn» «pn» { get { return «fn»; } }
    public override bool Equals(object __o) { ... }
    public override int GetHashCode() { ... }
}

dove ogni «Tx» è il tipo dell'espressione corrispondente «ex». L'espressione utilizzata in un member_declarator deve avere un tipo . Pertanto, si verifica un errore in fase di compilazione se un'espressione in un member_declarator è null o una funzione anonima.

I nomi di un tipo anonimo e del parametro al relativo metodo Equals vengono generati automaticamente dal compilatore e non possono essere referenziati nel testo del programma.

All'interno dello stesso programma, due inizializzatori di oggetti anonimi che specificano una sequenza di proprietà degli stessi nomi e tipi in fase di compilazione nello stesso ordine produrranno istanze dello stesso tipo anonimo.

Esempio: nell'esempio

var p1 = new { Name = "Lawnmower", Price = 495.00 };
var p2 = new { Name = "Shovel", Price = 26.95 };
p1 = p2;

l'assegnazione sull'ultima riga è consentita perché p1 e p2 sono dello stesso tipo anonimo.

esempio finale

I metodi Equals e GetHashcode su tipi anonimi eseguono l'override dei metodi ereditati da objecte vengono definiti in termini di Equals e GetHashcode delle proprietà, in modo che due istanze dello stesso tipo anonimo siano uguali se e solo se tutte le relative proprietà sono uguali.

Un dichiaratore membro può essere abbreviato in un nome semplice (§12.8.4), un accesso membro (§12.8.7), un inizializzatore di proiezione condizionale null §12.8.8 o un accesso di base (§12.8.15). Si tratta di un inizializzatore di proiezione ed è un modo abbreviato per dichiarare e assegnare una proprietà con lo stesso nome. In particolare, i dichiaratori membro dei moduli

«identifier», «expr» . «identifier» e «expr» ? . «identifier»

sono esattamente equivalenti rispettivamente ai seguenti:

«identifer» = «identifier», «identifier» = «expr» . «identifier» e «identifier» = «expr» ? . «identifier»

Pertanto, in un inizializzatore di proiezione l'identificatore seleziona sia il valore che il campo o la proprietà a cui è assegnato il valore. Intuitivamente, un inizializzatore di proiezione proietta non solo un valore, ma anche il nome del valore.

L'operatore typeof

L'operatore typeof viene utilizzato per ottenere l'oggetto System.Type per un tipo.

typeof_expression
    : 'typeof' '(' type ')'
    | 'typeof' '(' unbound_type_name ')'
    | 'typeof' '(' 'void' ')'
    ;

unbound_type_name
    : identifier generic_dimension_specifier?
    | identifier '::' identifier generic_dimension_specifier?
    | unbound_type_name '.' identifier generic_dimension_specifier?
    ;

generic_dimension_specifier
    : '<' comma* '>'
    ;

comma
    : ','
    ;

La prima forma di typeof_expression è costituita da una parola chiave typeof seguita da un tipo racchiuso tra parentesi. Il risultato di un'espressione di questo modulo è l'oggetto System.Type per il tipo indicato. Esiste un solo oggetto System.Type per qualsiasi tipo specificato. Ciò significa che per un tipo T, typeof(T) == typeof(T) è sempre vero. Il tipo non può essere dynamic.

La seconda forma di typeof_expression è costituita da una parola chiave typeof seguita da un unbound_type_nameracchiuso tra parentesi.

Nota: un unbound_type_name è molto simile a un type_name (§7.8), tranne che un unbound_type_name contiene generic_dimension_specifiermentre un type_name contiene type_argument_list. nota finale

Quando l'operando di un typeof_expression è una sequenza di token che soddisfa le grammatiche di entrambe unbound_type_name e type_name, ovvero quando non contiene né un generic_dimension_specifier né una type_argument_list, la sequenza di token viene considerata un type_name. Il significato di un unbound_type_name è determinato nel modo seguente:

  • Convertire la sequenza di token in un type_name sostituendo ogni generic_dimension_specifier con un type_argument_list con lo stesso numero di virgole e la parola chiave object di ogni type_argument.
  • Valutare il type_namerisultante, ignorando tutti i vincoli dei parametri di tipo.
  • Il unbound_type_name si risolve nel tipo generico non associato al tipo costruito risultante (§8.4).

Si tratta di un errore che il nome del tipo sia un tipo di riferimento nullable.

Il risultato dell'typeof_expression è l'oggetto System.Type per il tipo generico non vincolato risultante.

La terza forma di typeof_expression è costituita da una parola chiave typeof seguita da una parola chiave void racchiusa tra parentesi. Il risultato di un'espressione di questo form è l'oggetto System.Type che rappresenta l'assenza di un tipo. L'oggetto tipo restituito da typeof(void) è distinto da quello restituito per qualsiasi altro tipo.

Nota: questo speciale oggetto System.Type è utile nelle librerie di classi che consentono la reflection sui metodi nel linguaggio, dove tali metodi desiderano avere un modo per rappresentare il tipo restituito di qualsiasi metodo, inclusi i metodi void, con un'istanza di System.Type. nota finale

L'operatore typeof può essere usato in un parametro di tipo. Si tratta di un errore in fase di compilazione se il nome del tipo è noto come tipo di riferimento annullabile. Il risultato è l'oggetto System.Type per il tipo di esecuzione associato al parametro di tipo. Se il tipo di runtime è un tipo di riferimento annullabile, il risultato è il tipo di riferimento non annullabile corrispondente. L'operatore typeof può essere usato anche in un tipo costruito o in un tipo generico non associato (§8.4.4). L'oggetto System.Type per un tipo generico non vincolato non è lo stesso dell'oggetto System.Type del tipo di istanza (§15.3.2). Il tipo di istanza è sempre un tipo costruito chiuso in fase di esecuzione, pertanto il relativo oggetto System.Type dipende dagli argomenti del tipo di runtime in uso. Il tipo generico non associato, invece, non ha argomenti di tipo e restituisce lo stesso oggetto System.Type indipendentemente dagli argomenti del tipo di runtime.

Esempio: Esempio

class X<T>
{
    public static void PrintTypes()
    {
        Type[] t =
        {
            typeof(int),
            typeof(System.Int32),
            typeof(string),
            typeof(double[]),
            typeof(void),
            typeof(T),
            typeof(X<T>),
            typeof(X<X<T>>),
            typeof(X<>)
        };
        for (int i = 0; i < t.Length; i++)
        {
            Console.WriteLine(t[i]);
        }
    }
}

class Test
{
    static void Main()
    {
        X<int>.PrintTypes();
    }
}

produce l'output seguente:

System.Int32
System.Int32
System.String
System.Double[]
System.Void
System.Int32
X`1[System.Int32]
X`1[X`1[System.Int32]]
X`1[T]

Si noti che int e System.Int32 sono dello stesso tipo. Il risultato di typeof(X<>) non dipende dall'argomento di tipo, ma il risultato di typeof(X<T>) sì.

esempio finale

12.8.19 Operatore sizeof

L'operatore sizeof restituisce il numero di byte a 8 bit occupati da una variabile di un determinato tipo. Il tipo specificato come operando da sizeof deve essere un unmanaged_type (§8,8).

sizeof_expression
    : 'sizeof' '(' unmanaged_type ')'
    ;

Per determinati tipi predefiniti, l'operatore sizeof restituisce un valore int costante, come illustrato nella tabella seguente:

espressione Risultato
sizeof(sbyte) 1
sizeof(byte) 1
sizeof(short) 2
sizeof(ushort) 2
sizeof(int) 4
sizeof(uint) 4
sizeof(long) 8
sizeof(ulong) 8
sizeof(char) 2
sizeof(float) 4
sizeof(double) 8
sizeof(bool) 1
sizeof(decimal) 16

Per un tipo enum T, il risultato dell'espressione sizeof(T) è un valore costante uguale alle dimensioni del tipo sottostante, come indicato in precedenza. Per tutti gli altri tipi di operando, l'operatore sizeof è specificato in §23.6.9.

12.8.20 Operatori controllati e non controllati

Gli operatori checked e unchecked vengono usati per controllare il contesto di controllo dell'overflow per operazioni e conversioni aritmetiche di tipo integrale.

checked_expression
    : 'checked' '(' expression ')'
    ;

unchecked_expression
    : 'unchecked' '(' expression ')'
    ;

L'operatore checked valuta l'espressione contenuta in un contesto controllato e l'operatore unchecked valuta l'espressione contenuta in un contesto non selezionato. Un checked_expression o un unchecked_expression corrisponde esattamente a un parenthesized_expression (§12.8.5), a eccezione del fatto che l'espressione contenuta viene valutata nel contesto di controllo overflow specificato.

Il contesto di verifica dell'overflow può anche essere regolato tramite le istruzioni checked e unchecked (§13.12).

Le operazioni seguenti sono interessate dal contesto di controllo dell'overflow stabilito dagli operatori e dalle istruzioni checked e unchecked:

  • Operatori ++ e -- predefiniti (§12.8.16 e §12.9.6), quando l'operando è di tipo integrale o enumerazione.
  • Operatore unario predefinito - (§12.9.3), quando l'operando è di un tipo integrale.
  • Operatori binari predefiniti +, -, *e / (§12.10), quando entrambi gli operandi sono di tipi integrali o enumerati.
  • Conversioni numeriche esplicite (§10.3.2) da un tipo integrale o enumerazione a un altro tipo integrale o enumerazione oppure da float o double a un tipo integrale o enumerazione.

Quando una delle operazioni precedenti produce un risultato troppo grande da rappresentare nel tipo di destinazione, il contesto in cui viene eseguita l'operazione controlla il comportamento risultante:

  • In un contesto di checked, se l'operazione è un'espressione costante (§12.23), si verifica un errore in fase di compilazione. In caso contrario, quando l'operazione viene eseguita in fase di esecuzione, viene generata una System.OverflowException.
  • In un contesto unchecked, il risultato viene troncato rimuovendo tutti i bit di ordine elevato che non rientrano nel tipo di destinazione.

Per le espressioni non costanti (§12.23) (espressioni valutate in fase di esecuzione) che non sono racchiuse in operatori o istruzioni checked o unchecked, il contesto predefinito di controllo dell'overflow è non controllato, a meno che fattori esterni (come le opzioni del compilatore e la configurazione dell'ambiente di runtime) non richiedano una valutazione controllata.

Per le espressioni costanti (§12.23) (espressioni che possono essere valutate completamente in fase di compilazione), viene sempre controllato il contesto di controllo dell'overflow predefinito. A meno che un'espressione costante non venga inserita in modo esplicito in un contesto di unchecked, gli overflow che si verificano durante la valutazione in fase di compilazione dell'espressione causano sempre errori in fase di compilazione.

Il corpo di una funzione anonima non è influenzato dai contesti checked o unchecked in cui avviene la funzione anonima.

Esempio: nel codice seguente

class Test
{
    static readonly int x = 1000000;
    static readonly int y = 1000000;

    static int F() => checked(x * y);    // Throws OverflowException
    static int G() => unchecked(x * y);  // Returns -727379968
    static int H() => x * y;             // Depends on default
}

non vengono segnalati errori in fase di compilazione perché nessuna delle espressioni può essere valutata in fase di compilazione. In fase di esecuzione, il metodo F genera un System.OverflowExceptione il metodo G restituisce –727379968 (i 32 bit inferiori del risultato out-of-range). Il comportamento del metodo H dipende dal contesto di controllo overflow predefinito per la compilazione, ma è uguale a F o uguale a G.

esempio finale

Esempio: nel codice seguente

class Test
{
    const int x = 1000000;
    const int y = 1000000;

    static int F() => checked(x * y);    // Compile-time error, overflow
    static int G() => unchecked(x * y);  // Returns -727379968
    static int H() => x * y;             // Compile-time error, overflow
}

gli overflow che si verificano durante la valutazione delle espressioni costanti in F e H causano la segnalazione di errori in fase di compilazione perché le espressioni vengono valutate in un contesto di checked. Un overflow si verifica anche quando si valuta l'espressione costante in G, ma poiché la valutazione avviene in un contesto di unchecked, l'overflow non viene segnalato.

esempio finale

Gli operatori checked e unchecked influiscono solo sul contesto di controllo dell'overflow per le operazioni contenute testualmente all'interno dei token "(" e ")". Gli operatori non hanno alcun effetto sui membri della funzione richiamati in seguito alla valutazione dell'espressione contenuta.

Esempio: nel codice seguente

class Test
{
    static int Multiply(int x, int y) => x * y;

    static int F() => checked(Multiply(1000000, 1000000));
}

L'uso di checked in F non influisce sulla valutazione di x * y in Multiply, quindi x * y viene valutato nel contesto di controllo dell'overflow predefinito.

esempio finale

L'operatore unchecked è utile quando si scrivono costanti dei tipi integrali con segno nella notazione esadecimale.

esempio:

class Test
{
    public const int AllBits = unchecked((int)0xFFFFFFFF);
    public const int HighBit = unchecked((int)0x80000000);
}

Entrambe le costanti esadecimali precedenti sono di tipo uint. Poiché le costanti non rientrano nell'intervallo di int, senza l'operatore unchecked, i cast a int produrrebbero errori in fase di compilazione.

esempio finale

Nota: gli operatori e le istruzioni checked e unchecked consentono ai programmatori di controllare alcuni aspetti di alcuni calcoli numerici. Tuttavia, il comportamento di alcuni operatori numerici dipende dai tipi di dati degli operandi. Ad esempio, la moltiplicazione di due decimali risulta sempre in un'eccezione di overflow anche all'interno di un costrutto non verificato in modo esplicito. Analogamente, moltiplicare due float non produce mai un'eccezione per overflow, neppure all'interno di un costrutto controllato in modo esplicito. Inoltre, gli altri operatori non sono mai interessati dalla modalità di controllo, sia predefinita che esplicita. nota finale

12.8.21 Espressioni valore predefinito

Un'espressione di valore predefinita viene usata per ottenere il valore predefinito (§9,3) di un tipo.

default_value_expression
    : explictly_typed_default
    | default_literal
    ;

explictly_typed_default
    : 'default' '(' type ')'
    ;

default_literal
    : 'default'
    ;

Un default_literal rappresenta un valore predefinito (§9,3). Non dispone di un tipo, ma può essere convertito in qualsiasi tipo tramite una conversione letterale predefinita (§10.2.16).

Il risultato di un default_value_expression è l'impostazione predefinita (§9.3) del tipo esplicito in un explictly_typed_defaulto il tipo di destinazione della conversione per un default_value_expression.

Un default_value_expression è un'espressione costante (§12.23) se il tipo è uno dei seguenti:

  • un tipo di riferimento
  • un parametro di tipo noto come tipo riferimento (§8.2);
  • uno dei tipi di valore seguenti: sbyte, byte, short, ushort, int, uint, long, ulong, char, float, double, decimal, bool,; o
  • qualsiasi tipo di enumerazione.

Allocazione dello stack 12.8.22

Un'espressione di allocazione dello stack alloca un blocco di memoria dallo stack di esecuzione. Lo stack di esecuzione è un'area di memoria in cui vengono archiviate le variabili locali. Lo stack di esecuzione non fa parte dell'heap gestito. La memoria usata per l'archiviazione delle variabili locali viene ripristinata automaticamente quando la funzione corrente viene restituita.

Le regole del contesto sicuro per un'espressione di allocazione dello stack sono descritte in §16.4.12.7.

stackalloc_expression
    : 'stackalloc' unmanaged_type '[' expression ']'
    | 'stackalloc' unmanaged_type? '[' constant_expression? ']' stackalloc_initializer
    ;

stackalloc_initializer
     : '{' stackalloc_initializer_element_list '}'
     ;

stackalloc_initializer_element_list
     : stackalloc_element_initializer (',' stackalloc_element_initializer)* ','?
     ;
    
stackalloc_element_initializer
    : expression
    ;

Il unmanaged_type (§8.8) indica il tipo di elementi che verranno archiviati nella posizione appena allocata e l'espressione indica il numero di questi elementi. Insieme, specificano le dimensioni di allocazione necessarie. Il tipo di espressione deve essere convertibile in modo implicito al tipo int.

Poiché le dimensioni di un'allocazione dello stack non possono essere negative, specificare il numero di elementi come "constant_expression" che venga valutata come un valore negativo costituisce un errore in fase di compilazione.

In fase di esecuzione se il numero di elementi da allocare è un valore negativo, il comportamento non è definito. Se è zero, non viene eseguita alcuna allocazione e il valore restituito è definito dall'implementazione. Se non è disponibile memoria sufficiente per allocare gli elementi, viene generato un System.StackOverflowException.

Quando è presente un stackalloc_initializer:

  • Se il unmanaged_type viene omesso, è dedotto seguendo le regole per il miglior tipo comune (§12.6.3.15) per l'insieme di stackalloc_element_initializers.
  • Se constant_expression viene omesso, si deduce che sia il numero di stackalloc_element_initializers.
  • Se constant_expression è presente, sarà uguale al numero di stackalloc_element_initializers.

Ogni stackalloc_element_initializer deve avere una conversione implicita in unmanaged_type (§10.2). Il stackalloc_element_initializerinizializza gli elementi nella memoria allocata in ordine crescente, a partire dall'indice zero. In assenza di un stackalloc_initializer, il contenuto della memoria appena allocata non è definito.

Se una stackalloc_expression avviene direttamente come espressione di inizializzazione di una local_variable_declaration (§13.6.2), dove il local_variable_type è un tipo di puntatore (§23.3) o è dedotto (var), il stackalloc_expression produce un puntatore di tipo T* (§23.9). In questo caso, il stackalloc_expression deve essere visualizzato nel codice non sicuro. In caso contrario, il risultato di un stackalloc_expression è un'istanza di tipo Span<T>, dove T è il unmanaged_type:

  • Span<T> (§C.3) è un tipo di ref struct (§16.2.3), che presenta un blocco di memoria, in questo caso il blocco allocato dal stackalloc_expression, come raccolta indicizzata di elementi tipizzati (T).
  • La proprietà Length del risultato restituisce il numero di elementi allocati.
  • L'indicizzatore del risultato (§15,9) restituisce un variable_reference (§9,5) a un elemento del blocco allocato e viene controllato l'intervallo.

Gli inizializzatori di allocazione dello stack non sono consentiti nei blocchi di catch o di finally (§13.11).

Nota: non è possibile liberare in modo esplicito la memoria allocata usando stackalloc. nota finale

Tutti i blocchi di memoria allocati dallo stack creati durante l'esecuzione di un membro della funzione vengono eliminati automaticamente quando il membro della funzione viene restituito.

Ad eccezione dell'operatore stackalloc, C# non fornisce costrutti predefiniti per la gestione della memoria non gestita da Garbage Collection. Tali servizi vengono in genere forniti supportando librerie di classi o importate direttamente dal sistema operativo sottostante.

esempio:

// Memory uninitialized
Span<int> span1 = stackalloc int[3];
// Memory initialized
Span<int> span2 = stackalloc int[3] { -10, -15, -30 };
// Type int is inferred
Span<int> span3 = stackalloc[] { 11, 12, 13 };
// Error; result is int*, not allowed in a safe context
var span4 = stackalloc[] { 11, 12, 13 };
// Error; no conversion from Span<int> to Span<long>
Span<long> span5 = stackalloc[] { 11, 12, 13 };
// Converts 11 and 13, and returns Span<long> 
Span<long> span6 = stackalloc[] { 11, 12L, 13 };
// Converts all and returns Span<long>
Span<long> span7 = stackalloc long[] { 11, 12, 13 };
// Implicit conversion of Span<T>
ReadOnlySpan<int> span8 = stackalloc int[] { 10, 22, 30 };
// Implicit conversion of Span<T>
Widget<double> span9 = stackalloc double[] { 1.2, 5.6 };

public class Widget<T>
{
    public static implicit operator Widget<T>(Span<double> sp) { return null; }
}

Nel caso di span8, stackalloc restituisce un Span<int>, convertito da un operatore implicito in ReadOnlySpan<int>. Analogamente, per span9, il Span<double> risultante viene convertito nel tipo definito dall'utente Widget<double> usando la conversione, come illustrato. esempio finale

12.8.23 Operatore nameof

Un nameof_expression viene utilizzato per ottenere il nome di un'entità di programma come stringa costante.

nameof_expression
    : 'nameof' '(' named_entity ')'
    ;
    
named_entity
    : named_entity_target ('.' identifier type_argument_list?)*
    ;
    
named_entity_target
    : simple_name
    | 'this'
    | 'base'
    | predefined_type 
    | qualified_alias_member
    ;

Poiché nameof non è una parola chiave, un nameof_expression è sempre sintatticamente ambiguo con una chiamata del nome semplice nameof. Per motivi di compatibilità, se una ricerca di nome (§12.8.4) del nome nameof ha esito positivo, l'espressione viene considerata come un invocation_expression , indipendentemente dal fatto che la chiamata sia valida. In caso contrario, è un nameof_expression.

Le ricerche di accesso ai membri e ai nomi semplici vengono eseguite sul named_entity in fase di compilazione, seguendo le regole descritte in §12.8.4 e §12.8.7. Tuttavia, se la ricerca descritta in §12.8.4 e §12.8.7 genera un errore perché un membro dell'istanza è stato trovato in un contesto statico, un'espressione nameof non genera un errore di questo tipo.

Si tratta di un errore in fase di compilazione per un named_entity che designa un gruppo di metodi per avere un type_argument_list. È un errore di compilazione per un named_entity_target avere il tipo dynamic.

Un nameof_expression è un'espressione costante di tipo stringe non ha alcun effetto in fase di esecuzione. In particolare, il named_entity non viene valutato né preso in considerazione ai fini dell'analisi di assegnazione definita (§9.4.4.22). Il suo valore è l'ultimo identificatore del named_entity prima del type_argument_listfacoltativo finale, trasformato come segue:

  • Il prefisso "@", se usato, viene rimosso.
  • Ogni unicode_escape_sequence viene trasformato nel carattere Unicode corrispondente.
  • Tutti i caratteri di formattazione vengono rimossi.

Queste sono le stesse trasformazioni applicate in §6.4.3 durante il test dell'uguaglianza tra gli identificatori.

Esempio: Di seguito vengono illustrati i risultati di varie espressioni nameof, supponendo che un tipo generico List<T> dichiarato all'interno dello spazio dei nomi System.Collections.Generic:

using TestAlias = System.String;

class Program
{
    static void Main()
    {
        var point = (x: 3, y: 4);

        string n1 = nameof(System);                      // "System"
        string n2 = nameof(System.Collections.Generic);  // "Generic"
        string n3 = nameof(point);                       // "point"
        string n4 = nameof(point.x);                     // "x"
        string n5 = nameof(Program);                     // "Program"
        string n6 = nameof(System.Int32);                // "Int32"
        string n7 = nameof(TestAlias);                   // "TestAlias"
        string n8 = nameof(List<int>);                   // "List"
        string n9 = nameof(Program.InstanceMethod);      // "InstanceMethod"
        string n10 = nameof(Program.GenericMethod);      // "GenericMethod"
        string n11 = nameof(Program.NestedClass);        // "NestedClass"

        // Invalid
        // string x1 = nameof(List<>);            // Empty type argument list
        // string x2 = nameof(List<T>);           // T is not in scope
        // string x3 = nameof(GenericMethod<>);   // Empty type argument list
        // string x4 = nameof(GenericMethod<T>);  // T is not in scope
        // string x5 = nameof(int);               // Keywords not permitted
        // Type arguments not permitted for method group
        // string x6 = nameof(GenericMethod<Program>);
    }

    void InstanceMethod() { }

    void GenericMethod<T>()
    {
        string n1 = nameof(List<T>); // "List"
        string n2 = nameof(T);       // "T"
    }

    class NestedClass { }
}

Le parti potenzialmente sorprendenti di questo esempio sono la risoluzione di nameof(System.Collections.Generic) come solo "Generic" anziché come lo spazio dei nomi completo, e di nameof(TestAlias) come "TestAlias" anziché come "String". esempio finale

12.8.24 Espressioni di metodo anonime

Un anonymous_method_expression è uno dei due modi per definire una funzione anonima. Questi sono descritti ulteriormente in §12.19.

12.9 Operatori unari

12.9.1 Generale

Gli operatori unari sono +, -, ! (solo negazione logica §12.9.4), ~, ++, --, cast e await.

Nota: l'operatore postfix null-forgiving (§12.8.9), !, a causa della sua natura di sola compilazione e non sovraccaricabile, viene escluso dall'elenco precedente. nota finale

unary_expression
    : primary_expression
    | '+' unary_expression
    | '-' unary_expression
    | logical_negation_operator unary_expression
    | '~' unary_expression
    | pre_increment_expression
    | pre_decrement_expression
    | cast_expression
    | await_expression
    | pointer_indirection_expression    // unsafe code support
    | addressof_expression              // unsafe code support
    ;

pointer_indirection_expression (§23.6.2) e addressof_expression (§23.6.5) sono disponibili solo nel codice non sicuro (§23).

Se l'operando di un unary_expression ha il tipo in fase di compilazione dynamic, è associato dinamicamente (§12.3.3). In questo caso, il tipo in fase di compilazione del unary_expression è dynamic, e la risoluzione descritta di seguito verrà eseguita in fase di esecuzione usando il tipo di run-time dell'operando.

Operatore 12.9.2 Unary plus

Per un'operazione della forma +x, si applica la risoluzione dell'overload dell'operatore unario (§12.4.4) per selezionare una specifica implementazione dell'operatore. L'operando viene convertito nel tipo di parametro dell'operatore selezionato e il tipo del risultato è il tipo restituito dell'operatore. Gli operatori unari più predefiniti sono:

int operator +(int x);
uint operator +(uint x);
long operator +(long x);
ulong operator +(ulong x);
float operator +(float x);
double operator +(double x);
decimal operator +(decimal x);

Per ognuno di questi operatori, il risultato è semplicemente il valore dell'operando.

Anche le forme lifted (§12.4.8) degli operatori unari predefiniti non sollevati, come definiti in precedenza, sono anch'esse predefinite.

12.9.3 Operatore meno unario

Per un'operazione della forma –x, si applica la risoluzione dell'overload dell'operatore unario (§12.4.4) per selezionare una specifica implementazione dell'operatore. L'operando viene convertito nel tipo di parametro dell'operatore selezionato e il tipo del risultato è il tipo restituito dell'operatore. Gli operatori unari meno predefiniti sono:

  • Negazione integer:

    int operator –(int x);
    long operator –(long x);
    

    Il risultato viene calcolato sottraendo X da zero. Se il valore di X è il valore rappresentabile più piccolo del tipo di operando (−2³¹ per int o −2⁶³ per long), allora la negazione matematica di X non è rappresentabile all'interno del tipo di operando. Se ciò si verifica all'interno di un contesto checked, viene lanciato un System.OverflowException; se si verifica all'interno di un contesto unchecked, il risultato è il valore dell'operando e l'overflow non viene riportato.

    Se l'operando dell'operatore di negazione è di tipo uint, viene convertito nel tipo longe il tipo del risultato è long. Un'eccezione è la regola che consente di scrivere il valore di int−2147483648 (−2³¹) come valore letterale intero decimale (§6.4.5.3).

    Se l'operando dell'operatore di negazione è di tipo ulong, si verifica un errore in fase di compilazione. Un'eccezione è la regola che consente di scrivere il valore long−9223372036854775808 (−2⁶³) come valore letterale intero decimale (§6.4.5.3)

  • Negazione a virgola mobile:

    float operator –(float x);
    double operator –(double x);
    

    Il risultato è il valore di X con il relativo segno invertito. Se x è NaN, anche il risultato sarà NaN.

  • Negazione decimale:

    decimal operator –(decimal x);
    

    Il risultato viene calcolato sottraendo X da zero. La negazione decimale equivale all'uso dell'operatore unario meno di tipo System.Decimal.

Le forme sollevate (§12.4.8) degli operatori unari predefiniti sollevati, definiti in precedenza, sono anch'esse predefinite.

Operatore di negazione logica 12.9.4

Per un'operazione della forma !x, si applica la risoluzione dell'overload dell'operatore unario (§12.4.4) per selezionare una specifica implementazione dell'operatore. L'operando viene convertito nel tipo di parametro dell'operatore selezionato e il tipo del risultato è il tipo restituito dell'operatore. Esiste un solo operatore di negazione logico predefinito:

bool operator !(bool x);

Questo operatore calcola la negazione logica dell'operando: se l'operando è true, il risultato è false. Se l'operando è false, il risultato è true.

Le forme sollevate (§12.4.8) dell'operatore di negazione logica predefinito e non sollevato definito in precedenza sono anch'esse predefinite.

Nota: gli operatori di negazione logica del prefisso e postfix null-forgiving (§12.8.9), mentre rappresentati dallo stesso token lessicale (!), sono distinti. nota finale

12.9.5 Operatore di complemento bit per bit

Per un'operazione della forma ~x, si applica la risoluzione dell'overload dell'operatore unario (§12.4.4) per selezionare una specifica implementazione dell'operatore. L'operando viene convertito nel tipo di parametro dell'operatore selezionato e il tipo del risultato è il tipo restituito dell'operatore. Gli operatori di complemento bit per bit predefiniti sono:

int operator ~(int x);
uint operator ~(uint x);
long operator ~(long x);
ulong operator ~(ulong x);

Per ognuno di questi operatori, il risultato dell'operazione è il complemento bit per bit di x.

Ogni tipo di enumerazione E fornisce in modo implicito l'operatore di complemento bit per bit seguente:

E operator ~(E x);

Il risultato della valutazione di ~x, dove X è un'espressione di un tipo di enumerazione E con un tipo sottostante U, equivale esattamente alla valutazione di (E)(~(U)x), ad eccezione del fatto che la conversione in E viene sempre eseguita come se in un contesto di unchecked (§12.8.20).

Lifted (§12.4.8) forme degli operatori di complemento bit per bit predefiniti definiti sopra sono anch'esse predefinite.

12.9.6 Operatori di incremento e decremento del prefisso

pre_increment_expression
    : '++' unary_expression
    ;

pre_decrement_expression
    : '--' unary_expression
    ;

L'operando di un'operazione di incremento o decremento del prefisso deve essere un'espressione classificata come variabile, accesso alle proprietà o accesso a un indicizzatore. Il risultato dell'operazione è un valore dello stesso tipo dell'operando.

Se l'operando di un'operazione prefissata di incremento o di decremento è un accesso a una proprietà o a un indicizzatore, la proprietà o l'indicizzatore devono avere sia un accesso get che un accesso set. In caso contrario, si verifica un errore di tempo di legame.

La risoluzione dell'overload dell'operatore unario (§12.4.4) viene applicata per selezionare un'implementazione specifica dell'operatore. Esistono operatori predefiniti ++ e -- per i tipi seguenti: sbyte, byte, short, ushort, int, uint, long, ulong, char, float, double, decimale qualsiasi tipo di enumerazione. Gli operatori ++ predefiniti restituiscono il valore prodotto aggiungendo 1 all'operando e gli operatori -- predefiniti restituiscono il valore prodotto sottraendo 1 dall'operando. In un contesto di checked, se il risultato di questa addizione o sottrazione non è compreso nell'intervallo del tipo di risultato e il tipo di risultato è un tipo integrale o un tipo enumerato, viene lanciato un System.OverflowException.

È prevista una conversione implicita dal tipo restituito dell'operatore unario selezionato al tipo di unary_expression, altrimenti si verifica un errore in fase di compilazione.

L'elaborazione in fase di esecuzione di un'operazione di incremento o decremento del prefisso del modulo ++x o --x è costituita dai passaggi seguenti:

  • Se x è classificato come variabile:
    • x viene valutato per produrre la variabile.
    • Il valore di x viene convertito nel tipo operando dell'operatore selezionato e l'operatore viene richiamato con questo valore come argomento.
    • Il valore restituito dall'operatore viene convertito nel tipo di x. Il valore risultante viene archiviato nella posizione specificata dalla valutazione di x.
    • e diventa il risultato dell'operazione.
  • Se x è classificato come accesso a proprietà o indicizzatore:
    • L'espressione di istanza (se x non è static) e l'elenco di argomenti (se x è un accesso indicizzatore) associato a x vengono valutati e i risultati vengono usati nelle chiamate delle funzioni di accesso get e set successive.
    • Viene richiamata la funzione di accesso get di x.
    • Il valore restituito dalla funzione di accesso get viene convertito nel tipo operando dell'operatore selezionato e l'operatore viene richiamato con questo valore come argomento.
    • Il valore restituito dall'operatore viene convertito nel tipo di x. La funzione di accesso set di x viene richiamata con questo valore come argomento del valore.
    • Questo valore diventa anche il risultato dell'operazione.

Gli operatori ++ e -- supportano anche la notazione postfix (§12.8.16). Il risultato di x++ o x-- è il valore di x prima dell'operazione, mentre il risultato di ++x o --x è il valore di x dopo l'operazione. In entrambi i casi, x ha lo stesso valore dopo l'operazione.

È possibile richiamare un'implementazione di operatori ++ o -- utilizzando la notazione prefissa o postfissa. Non è possibile avere implementazioni di operatori separate per le due notazioni.

Le forme sollevate (§12.4.8) degli operatori predefiniti di incremento e decremento non sollevati, definiti sopra, sono anch'esse predefinite.

12.9.7 Espressioni cast

Un cast_expression viene usato per convertire in modo esplicito un'espressione in un determinato tipo.

cast_expression
    : '(' type ')' unary_expression
    ;

Un cast_expression della forma (T)E, dove T è un tipo e E è un espressione unaria, esegue una conversione esplicita (§10.3) del valore di E al tipo T. Se non esiste alcuna conversione esplicita da E a T, si verifica un errore di binding-time. In caso contrario, il risultato è il valore prodotto dalla conversione esplicita. Il risultato viene sempre classificato come valore, anche se E indica una variabile.

La grammatica per un cast_expression porta ad alcune ambiguità sintattiche.

esempio: l'espressione (x)–y può essere interpretata come cast_expression (un cast di –y al tipo x) o come additive_expression combinata con un parenthesized_expression (che calcola il valore x – y). esempio finale

Per risolvere cast_expression ambiguità, esiste la regola seguente: una sequenza di uno o più token (§6.4) racchiusa tra parentesi viene considerata l'inizio di un cast_expression solo se almeno uno dei seguenti è vero:

  • La sequenza di token è una grammatica corretta per un tipo, ma non per un'espressione.
  • La sequenza di token è una grammatica corretta per un tipo, e il token subito dopo le parentesi di chiusura è il token "~", il token "!", il token "(", un identificatore (§6.4.3), un valore letterale (§6.4.5) o qualsiasi parola chiave (§6.4.4) ad eccezione di as e is.

Il termine "grammatica corretta" sopra indica solo che la sequenza di token deve essere conforme alla particolare produzione grammaticale. In particolare, non considera il significato effettivo di alcun identificatore costitutivo.

esempio: se x e y sono identificatori, x.y è una grammatica corretta per un tipo, anche se x.y non indica effettivamente un tipo. esempio finale

Nota: dalla regola di disambiguazione, ne consegue che, se x e y sono identificatori, (x)y, (x)(y)e (x)(-y) sono cast_expressions, ma (x)-y non è, anche se x identifica un tipo. Tuttavia, se x è una parola chiave che identifica un tipo predefinito (ad esempio int), tutte e quattro le forme sono cast_expressions (perché tale parola chiave non potrebbe essere un'espressione da sola). nota finale

12.9.8 Espressioni Await

12.9.8.1 Generale

L'operatore await viene usato per sospendere la valutazione della funzione asincrona di inclusione fino al completamento dell'operazione asincrona rappresentata dall'operando.

await_expression
    : 'await' unary_expression
    ;

Un await_expression è consentito soltanto nel corpo di una funzione asincrona (§15.15). All'interno della funzione asincrona più vicina, un await_expression non deve avvenire in queste posizioni:

  • All'interno di una funzione anonima annidata (non asincrona)
  • All'interno del blocco di un lock_statement
  • In una conversione di funzione anonima in un tipo di albero delle espressioni (§10.7.3)
  • In un contesto non sicuro

Nota: un await_expression non può verificarsi nella maggior parte delle posizioni all'interno di un query_expression, perché queste vengono trasformate sintatticamente per l'uso di espressioni lambda non asincrone. nota finale

All'interno di una funzione asincrona, await non dovrebbe essere usato come available_identifier, sebbene possa essere utilizzato l'identificatore verbatim @await. Non esiste quindi alcuna ambiguità sintattica tra await_expressione varie espressioni che coinvolgono identificatori. Al di fuori delle funzioni asincrone, await funge da identificatore normale.

L'operando di un await_expression è chiamato attività. Rappresenta un'operazione asincrona che può essere completata o meno al momento della valutazione del await_expression. Lo scopo dell'operatore await è sospendere l'esecuzione della funzione asincrona che la contiene fino al completamento dell'attività attesa e quindi ottenere il relativo risultato.

12.9.8.2 Espressioni aspettabili

L'attività di un await_expression deve essere awaitable. Un'espressione t è awaitable se si verifica una delle seguenti condizioni.

  • t è di tipo in fase di compilazione dynamic
  • t ha un metodo di istanza o estensione accessibile denominato GetAwaiter privo di parametri e di parametri di tipo, e un tipo di ritorno A per il quale valgono tutte le seguenti condizioni:
    • A implementa l'interfaccia System.Runtime.CompilerServices.INotifyCompletion (in questo caso nota come INotifyCompletion per brevità)
    • A dispone di una proprietà dell'istanza accessibile e leggibile IsCompleted di tipo bool
    • A dispone di un metodo di istanza accessibile GetResult senza parametri e senza parametri di tipo

Lo scopo del metodo è ottenere un awaiter per l'attività. Il tipo A viene chiamato tipo awaiter per l'espressione await.

Lo scopo della proprietà IsCompleted è determinare se l'attività è già completa. In tal caso, non è necessario sospendere la valutazione.

Lo scopo del metodo INotifyCompletion.OnCompleted consiste nel definire una "continuazione" per l'attività, cioè un delegato (di tipo System.Action) che verrà invocato una volta completata l'attività.

Lo scopo del metodo GetResult è ottenere il risultato dell'attività al termine dell'operazione. Questo risultato può essere portato a termine con successo, possibilmente con un valore di risultato, oppure può essere un'eccezione generata dal metodo GetResult.

12.9.8.3 Classificazione delle espressioni await

L'espressione await t viene classificata allo stesso modo dell'espressione (t).GetAwaiter().GetResult(). Pertanto, se il tipo restituito di GetResult è void, il await_expression viene classificato come nulla. Se il tipo restituito non èvoidT, il await_expression viene classificato come un valore di tipo T.

12.9.8.4 Valutazione in fase di esecuzione delle espressioni await

In fase di esecuzione, l'espressione await t viene valutata come segue:

  • Un awaiter a viene ottenuto valutando l'espressione (t).GetAwaiter().
  • Un boolb viene ottenuto valutando l'espressione (a).IsCompleted.
  • Se b è false, la valutazione dipende dal fatto che a implementi l'interfaccia System.Runtime.CompilerServices.ICriticalNotifyCompletion (in questo caso nota come ICriticalNotifyCompletion per brevità). Questo controllo viene eseguito in fase di associazione; ovvero, in fase di esecuzione se il tipo in fase di compilazione di a è dynamice in fase di compilazione in caso contrario. Lasciare che r indichi il delegato per la ripresa (§15.15):
    • Se a non implementa ICriticalNotifyCompletion, viene valutata l'espressione ((a) as INotifyCompletion).OnCompleted(r).
    • Se a implementa ICriticalNotifyCompletion, viene valutata l'espressione ((a) as ICriticalNotifyCompletion).UnsafeOnCompleted(r).
    • La valutazione viene quindi sospesa e il controllo viene ridato al chiamante attuale della funzione asincrona.
  • Immediatamente dopo (se b è true) o al momento della chiamata successiva del delegato di ripresa (se b è false), viene valutata l'espressione (a).GetResult(). Se restituisce un valore, tale valore è il risultato del await_expression. Altrimenti, il risultato è nullo.

L'implementazione di un awaiter dei metodi di interfaccia INotifyCompletion.OnCompleted e ICriticalNotifyCompletion.UnsafeOnCompleted dovrebbe provocare al massimo una sola chiamata del delegato r. In caso contrario, il comportamento della funzione asincrona che la contiene non è definito.

12.10 Operatori aritmetici

12.10.1 Generale

Gli operatori *, /, %, +e - vengono chiamati operatori aritmetici.

multiplicative_expression
    : unary_expression
    | multiplicative_expression '*' unary_expression
    | multiplicative_expression '/' unary_expression
    | multiplicative_expression '%' unary_expression
    ;

additive_expression
    : multiplicative_expression
    | additive_expression '+' multiplicative_expression
    | additive_expression '-' multiplicative_expression
    ;

Se un operando di un operatore aritmetico ha il tipo in fase di compilazione dynamic, l'espressione viene associata dinamicamente (§12.3.3). In questo caso, il tipo in fase di compilazione dell'espressione è dynamice la risoluzione descritta di seguito avverrà a runtime, usando il tipo di runtime degli operandi che hanno il tipo in fase di compilazione dynamic.

Operatore di moltiplicazione 12.10.2

Per un'operazione della forma x * y, viene applicata la risoluzione dell'overload dell'operatore binario (§12.4.5) per selezionare un'implementazione specifica dell'operatore. Gli operandi vengono convertiti nei tipi di parametro dell'operatore selezionato e il tipo del risultato è il tipo restituito dell'operatore.

Di seguito sono elencati gli operatori di moltiplicazione predefiniti. Tutti gli operatori calcolano il prodotto di x e y.

  • Moltiplicazione di numeri interi:

    int operator *(int x, int y);
    uint operator *(uint x, uint y);
    long operator *(long x, long y);
    ulong operator *(ulong x, ulong y);
    

    In un contesto di checked, se il prodotto non rientra nell'intervallo del tipo risultato, viene lanciata una System.OverflowException. In un contesto di unchecked, gli overflow non vengono segnalati ed eventuali bit significativi di ordine elevato al di fuori dell'intervallo del tipo di risultato vengono eliminati.

  • Moltiplicazione a virgola mobile:

    float operator *(float x, float y);
    double operator *(double x, double y);
    

    Il prodotto viene calcolato in base alle regole dell'aritmetica IEC 60559. Nella tabella seguente sono elencati i risultati di tutte le possibili combinazioni di valori finiti diversi da zero, zeri, infiniti e NaN. Nella tabella x e y sono valori finiti positivi. z è il risultato di x * y, arrotondato al valore rappresentabile più vicino. Se la grandezza del risultato è troppo grande per il tipo di destinazione, z è infinito. A causa dell'arrotondamento, z può essere zero anche se né xy è zero.

    +y -y +0 -0 +∞ -∞ NaN
    +x +z -z +0 -0 +∞ -∞ NaN
    -x -z +z -0 +0 -∞ +∞ NaN
    +0 +0 -0 +0 -0 NaN NaN NaN
    -0 -0 +0 -0 +0 NaN NaN NaN
    +∞ +∞ -∞ NaN NaN +∞ -∞ NaN
    -∞ -∞ +∞ NaN NaN -∞ +∞ NaN
    NaN NaN NaN NaN NaN NaN NaN NaN

    (Se non diversamente indicato, nelle tabelle a virgola mobile in §12.10.2§12.10.6 l'uso di "+" indica che il valore è positivo; l'uso di "-" indica che il valore è negativo; e la mancanza di un segno indica che il valore può essere positivo o negativo o non ha alcun segno (NaN).

  • Moltiplicazione decimale:

    decimal operator *(decimal x, decimal y);
    

    Se la grandezza del valore risultante è troppo grande per rappresentare nel formato decimale, viene generata una System.OverflowException. A causa dell'arrotondamento, il risultato può essere zero anche se nessuno degli operandi è zero. La scala del risultato, prima di qualsiasi arrotondamento, è la somma delle scale dei due operandi. La moltiplicazione decimale equivale all'uso dell'operatore di moltiplicazione di tipo System.Decimal.

Sono anche predefinite le forme sollevate (§12.4.8) degli operatori di moltiplicazione predefiniti non sollevati definiti in precedenza.

Operatore di divisione 12.10.3

Per un'operazione della forma x / y, viene applicata la risoluzione dell'overload dell'operatore binario (§12.4.5) per selezionare un'implementazione specifica dell'operatore. Gli operandi vengono convertiti nei tipi di parametro dell'operatore selezionato e il tipo del risultato è il tipo restituito dell'operatore.

Di seguito sono elencati gli operatori di divisione predefiniti. Tutti gli operatori calcolano il quoziente di x e y.

  • Divisione integer:

    int operator /(int x, int y);
    uint operator /(uint x, uint y);
    long operator /(long x, long y);
    ulong operator /(ulong x, ulong y);
    

    Se il valore dell'operando destro è zero, viene generata una System.DivideByZeroException.

    La divisione arrotonda il risultato verso zero. Il valore assoluto del risultato è quindi il numero intero più grande possibile minore o uguale al valore assoluto del quoziente dei due operandi. Il risultato è zero o positivo quando i due operandi hanno lo stesso segno e zero o negativo quando i due operandi hanno segni opposti.

    Se l'operando di sinistra è il valore più piccolo rappresentabile int o long e l'operando di destra è –1, si verifica un overflow. In un contesto di checked, viene generata una System.ArithmeticException (o una sottoclasse). In un contesto di unchecked, è definito dall'implementazione se venga generata una System.ArithmeticException (o una sottoclasse) o che l'overflow venga non segnalato con il valore risultante pari a quello dell'operando sinistro.

  • Divisione a virgola mobile:

    float operator /(float x, float y);
    double operator /(double x, double y);
    

    Il quoziente viene calcolato in base alle regole dell'aritmetica IEC 60559. Nella tabella seguente sono elencati i risultati di tutte le possibili combinazioni di valori finiti diversi da zero, zeri, infiniti e NaN. Nella tabella x e y sono valori finiti positivi. z è il risultato di x / y, arrotondato al valore rappresentabile più vicino.

    +y -y +0 -0 +∞ -∞ NaN
    +x +z -z +∞ -∞ +0 -0 NaN
    -x -z +z -∞ +∞ -0 +0 NaN
    +0 +0 -0 NaN NaN +0 -0 NaN
    -0 -0 +0 NaN NaN -0 +0 NaN
    +∞ +∞ -∞ +∞ -∞ NaN NaN NaN
    -∞ -∞ +∞ -∞ +∞ NaN NaN NaN
    NaN NaN NaN NaN NaN NaN NaN NaN
  • Divisione decimale:

    decimal operator /(decimal x, decimal y);
    

    Se il valore dell'operando destro è zero, viene generata una System.DivideByZeroException. Se la grandezza del valore risultante è troppo grande per rappresentare nel formato decimale, viene generata una System.OverflowException. A causa dell'arrotondamento, il risultato può essere zero anche se il primo operando non è zero. La scala del risultato, prima di qualsiasi arrotondamento, è la scala più vicina alla scala preferita che manterrà un risultato uguale al risultato esatto. La scala preferita è la scala di x meno la scala di y.

    La divisione decimale equivale all'uso dell'operatore di divisione di tipo System.Decimal.

Anche le forme sollevate predefinite (§12.4.8) degli operatori di divisione predefiniti non sollevati definiti in precedenza sono predefinite.

Operatore resto 12.10.4

Per un'operazione della forma x % y, viene applicata la risoluzione dell'overload dell'operatore binario (§12.4.5) per selezionare un'implementazione specifica dell'operatore. Gli operandi vengono convertiti nei tipi di parametro dell'operatore selezionato e il tipo del risultato è il tipo restituito dell'operatore.

Gli operatori di resto predefiniti sono elencati di seguito. Tutti gli operatori calcolano il resto della divisione tra x e y.

  • Resto intero:

    int operator %(int x, int y);
    uint operator %(uint x, uint y);
    long operator %(long x, long y);
    ulong operator %(ulong x, ulong y);
    

    Il risultato di x % y è il valore generato da x – (x / y) * y. Se y è zero, viene lanciata una System.DivideByZeroException.

    Se l'operando sinistro è il valore più piccolo int o long e l'operando destro è –1, viene generato un System.OverflowException solo se e quando x / y genererà un'eccezione.

  • Resto a virgola mobile:

    float operator %(float x, float y);
    double operator %(double x, double y);
    

    Nella tabella seguente sono elencati i risultati di tutte le possibili combinazioni di valori finiti diversi da zero, zeri, infiniti e NaN. Nella tabella x e y sono valori finiti positivi. z è il risultato di x % y e viene calcolato come x – n * y, dove n è il numero intero più grande possibile minore o uguale a x / y. Questo metodo di calcolo del resto è analogo a quello usato per gli operandi interi, ma differisce dalla definizione IEC 60559 (in cui n è il numero intero più vicino a x / y).

    +y -y +0 -0 +∞ -∞ NaN
    +x +z +z NaN NaN +x +x NaN
    -x -z -z NaN NaN -x -x NaN
    +0 +0 +0 NaN NaN +0 +0 NaN
    -0 -0 -0 NaN NaN -0 -0 NaN
    +∞ NaN NaN NaN NaN NaN NaN NaN
    -∞ NaN NaN NaN NaN NaN NaN NaN
    NaN NaN NaN NaN NaN NaN NaN NaN
  • Resto decimale:

    decimal operator %(decimal x, decimal y);
    

    Se il valore dell'operando destro è zero, viene generata una System.DivideByZeroException. È definito dall'implementazione quando viene lanciata una System.ArithmeticException (o una sottoclasse). Un'implementazione conforme non genera un'eccezione per x % y in qualsiasi caso in cui x / y non generi un'eccezione. La scala del risultato, prima di qualsiasi arrotondamento, è maggiore delle scale dei due operandi e il segno del risultato, se diverso da zero, è uguale a quello di x.

    Il resto decimale equivale all'uso dell'operatore resto di tipo System.Decimal.

    Nota: Queste regole garantiscono che per tutti i tipi il risultato non abbia mai un segno opposto a quello dell'operando sinistro. nota finale

Anche le forme sollevate (§12.4.8) degli operatori di resto predefiniti sollevati definiti precedentemente sono predefinite.

12.10.5 Operatore di addizione

Per un'operazione della forma x + y, viene applicata la risoluzione dell'overload dell'operatore binario (§12.4.5) per selezionare un'implementazione specifica dell'operatore. Gli operandi vengono convertiti nei tipi di parametro dell'operatore selezionato e il tipo del risultato è il tipo restituito dell'operatore.

Di seguito sono elencati gli operatori di addizione predefiniti. Per i tipi numerici ed enumerazioni, gli operatori di addizione predefiniti calcolano la somma dei due operandi. Quando uno o entrambi gli operandi sono di tipo string, gli operatori di addizione predefiniti concatenano la rappresentazione di stringa degli operandi.

  • Addizione di numeri interi:

    int operator +(int x, int y);
    uint operator +(uint x, uint y);
    long operator +(long x, long y);
    ulong operator +(ulong x, ulong y
    

    In un contesto checked, se la somma supera l'intervallo del tipo di risultato, viene lanciato un System.OverflowException. In un contesto di unchecked, gli overflow non vengono segnalati ed eventuali bit significativi di ordine elevato al di fuori dell'intervallo del tipo di risultato vengono eliminati.

  • Addizione a virgola mobile

    float operator +(float x, float y);
    double operator +(double x, double y);
    

    La somma viene calcolata in base alle regole dell'aritmetica IEC 60559. Nella tabella seguente sono elencati i risultati di tutte le possibili combinazioni di valori finiti diversi da zero, zeri, infiniti e NaN. Nella tabella x e y sono valori finiti diversi da zero e z è il risultato di x + y. Se x e y hanno la stessa grandezza ma segni opposti, z è zero positivo. Se x + y è troppo grande per essere rappresentato nel tipo di destinazione, z è un infinito con lo stesso segno di x + y.

    y +0 -0 +∞ -∞ NaN
    x z x x +∞ -∞ NaN
    +0 y +0 +0 +∞ –∞ NaN
    -0 y +0 -0 +∞ -∞ NaN
    +∞ +∞ +∞ +∞ +∞ NaN NaN
    -∞ -∞ -∞ -∞ NaN -∞ NaN
    NaN NaN NaN NaN NaN NaN NaN
  • Addizione decimale:

    decimal operator +(decimal x, decimal y);
    

    Se la grandezza del valore risultante è troppo grande per rappresentare nel formato decimale, viene generata una System.OverflowException. La scala del risultato, prima di qualsiasi arrotondamento, è quella maggiore tra le scale dei due operandi.

    L'addizione decimale equivale all'uso dell'operatore di addizione di tipo System.Decimal.

  • Aggiunta dell'enumerazione. Ogni tipo di enumerazione fornisce in modo implicito gli operatori predefiniti seguenti, dove E è il tipo di enumerazione e U è il tipo sottostante di E:

    E operator +(E x, U y);
    E operator +(U x, E y);
    

    In fase di esecuzione questi operatori vengono valutati esattamente come (E)((U)x + (U)y).

  • Concatenazione di stringhe:

    string operator +(string x, string y);
    string operator +(string x, object y);
    string operator +(object x, string y);
    

    Questi sovraccarichi dell'operatore binario + eseguono la concatenazione di stringhe. Se un operando di concatenazione di stringhe è null, viene sostituita una stringa vuota. In caso contrario, qualsiasi operando nonstring viene convertito nella relativa rappresentazione di stringa richiamando il metodo ToString virtuale ereditato dal tipo object. Se ToString restituisce null, viene sostituita una stringa vuota.

    esempio:

    class Test
    {
        static void Main()
        {
            string s = null;
            Console.WriteLine("s = >" + s + "<");  // Displays s = ><
    
            int i = 1;
            Console.WriteLine("i = " + i);         // Displays i = 1
    
            float f = 1.2300E+15F;
            Console.WriteLine("f = " + f);         // Displays f = 1.23E+15
    
            decimal d = 2.900m;
            Console.WriteLine("d = " + d);         // Displays d = 2.900
       }
    }
    

    L'output visualizzato nei commenti è il risultato tipico su un sistema US-English. L'output preciso può dipendere dalle impostazioni regionali dell'ambiente di esecuzione. L'operatore di concatenazione di stringhe si comporta allo stesso modo in ogni caso, ma i metodi ToString chiamati in modo implicito durante l'esecuzione potrebbero essere interessati dalle impostazioni internazionali.

    esempio finale

    Il risultato dell'operatore di concatenazione di stringhe è un string costituito dai caratteri dell'operando sinistro seguito dai caratteri dell'operando destro. L'operatore di concatenazione di stringhe non restituisce mai un valore null. È possibile che venga generata una System.OutOfMemoryException se non è disponibile memoria sufficiente per allocare la stringa risultante.

  • Combinazione di delegati. Ogni tipo di delegato fornisce in modo implicito l'operatore predefinito seguente, in cui D è il tipo delegato:

    D operator +(D x, D y);
    

    Se il primo operando è null, il risultato dell'operazione è il valore del secondo operando (anche se è anche null). In caso contrario, se il secondo operando è null, il risultato dell'operazione è il valore del primo operando. In caso contrario, il risultato dell'operazione è una nuova istanza del delegato il cui elenco chiamate è costituito dagli elementi nell'elenco chiamate del primo operando, seguito dagli elementi nell'elenco chiamate del secondo operando. Ovvero, l'elenco chiamate del delegato risultante è la concatenazione degli elenchi di chiamate dei due operandi.

    Nota: Per esempi di combinazione di delegati, vedere §12.10.6 e §20.6. Poiché System.Delegate non è un tipo delegato, l'operatore + non è definito per esso. nota finale

Anche le forme di sollevamento (§12.4.8) degli operatori di addizione predefiniti non sollevati definiti sopra sono anch'esse predefinite.

12.10.6 Operatore di sottrazione

Per un'operazione della forma x – y, viene applicata la risoluzione dell'overload dell'operatore binario (§12.4.5) per selezionare un'implementazione specifica dell'operatore. Gli operandi vengono convertiti nei tipi di parametro dell'operatore selezionato e il tipo del risultato è il tipo restituito dell'operatore.

Di seguito sono elencati gli operatori di sottrazione predefiniti. Tutti gli operatori sottraggono y da x.

  • Sottrazione di numeri interi

    int operator –(int x, int y);
    uint operator –(uint x, uint y);
    long operator –(long x, long y);
    ulong operator –(ulong x, ulong y
    

    In un contesto checked, se la differenza non rientra nell'intervallo del tipo di risultato, viene generata una System.OverflowException. In un contesto di unchecked, gli overflow non vengono segnalati ed eventuali bit significativi di ordine elevato al di fuori dell'intervallo del tipo di risultato vengono eliminati.

  • Sottrazione a virgola mobile:

    float operator –(float x, float y);
    double operator –(double x, double y);
    

    La differenza viene calcolata in base alle regole dell'aritmetica IEC 60559. Nella tabella seguente sono elencati i risultati di tutte le possibili combinazioni di valori finiti diversi da zero, zeri, infiniti e NaN. Nella tabella x e y sono valori finiti diversi da zero e z è il risultato di x – y. Se x e y sono uguali, z è zero positivo. Se x – y è troppo grande per essere rappresentato nel tipo di destinazione, z è un infinito con lo stesso segno di x – y.

    y +0 -0 +∞ -∞ NaN
    x z x x -∞ +∞ NaN
    +0 -y +0 +0 -∞ +∞ NaN
    -0 -y -0 +0 -∞ +∞ NaN
    +∞ +∞ +∞ +∞ NaN +∞ NaN
    -∞ -∞ -∞ -∞ -∞ NaN NaN
    NaN NaN NaN NaN NaN NaN NaN

    Nella tabella precedente le voci di -y indicano la negazione di y, non che il valore sia negativo.

  • Sottrazione decimale:

    decimal operator –(decimal x, decimal y);
    

    Se la grandezza del valore risultante è troppo grande per rappresentare nel formato decimale, viene generata una System.OverflowException. La scala del risultato, prima di qualsiasi arrotondamento, è quella maggiore tra le scale dei due operandi.

    La sottrazione decimale equivale all'uso dell'operatore di sottrazione di tipo System.Decimal.

  • Sottrazione di enumerazione. Ogni tipo di enumerazione fornisce in modo implicito l'operatore predefinito seguente, dove E è il tipo di enumerazione e U è il tipo sottostante di E:

    U operator –(E x, E y);
    

    Questo operatore viene valutato esattamente come (U)((U)x – (U)y). In altre parole, l'operatore calcola la differenza tra i valori ordinali di x e ye il tipo del risultato è il tipo sottostante dell'enumerazione.

    E operator –(E x, U y);
    

    Questo operatore viene valutato esattamente come (E)((U)x – y). In altre parole, l'operatore sottrae un valore dal tipo sottostante dell'enumerazione, producendo un valore dell'enumerazione .

  • Rimozione del delegato. Ogni tipo di delegato fornisce in modo implicito l'operatore predefinito seguente, in cui D è il tipo delegato:

    D operator –(D x, D y);
    

    La semantica è la seguente:

    • Se il primo operando è null, il risultato dell'operazione è null.
    • In caso contrario, se il secondo operando è null, il risultato dell'operazione è il valore del primo operando.
    • In caso contrario, entrambi gli operandi rappresentano elenchi di chiamate non vuoti (§20.2).
      • Se gli elenchi sono uguali, come determinato dall'operatore di uguaglianza di delegato (§12.12.9), il risultato dell'operazione è null.
      • In caso contrario, il risultato dell'operazione è un nuovo elenco di chiamate costituito dall'elenco del primo operando con le voci del secondo operando rimosse da esso, purché l'elenco del secondo operando sia una sottolista del primo. Per determinare l'uguaglianza dell'elenco secondario, le voci corrispondenti vengono confrontate come per l'operatore di uguaglianza delegato. Se l'elenco del secondo operando corrisponde a più sottoliste di voci contigue nell'elenco del primo operando, viene rimosso l'ultimo sottolista corrispondente di voci contigue.
      • In caso contrario, il risultato dell'operazione è il valore dell'operando a sinistra.

    Nessuno dei rispettivi elenchi degli operandi (se presenti) viene modificato nel processo.

    esempio:

    delegate void D(int x);
    
    class C
    {
        public static void M1(int i) { ... }
        public static void M2(int i) { ... }
    }
    
    class Test
    {
        static void Main()
        {
            D cd1 = new D(C.M1);
            D cd2 = new D(C.M2);
            D list = null;
    
            list = null - cd1;                             // null
            list = (cd1 + cd2 + cd2 + cd1) - null;         // M1 + M2 + M2 + M1
            list = (cd1 + cd2 + cd2 + cd1) - cd1;          // M1 + M2 + M2
            list = (cd1 + cd2 + cd2 + cd1) - (cd1 + cd2);  // M2 + M1
            list = (cd1 + cd2 + cd2 + cd1) - (cd2 + cd2);  // M1 + M1
            list = (cd1 + cd2 + cd2 + cd1) - (cd2 + cd1);  // M1 + M2
            list = (cd1 + cd2 + cd2 + cd1) - (cd1 + cd1);  // M1 + M2 + M2 + M1
            list = (cd1 + cd2 + cd2 + cd1) - (cd1 + cd2 + cd2 + cd1);  // null
        }
    }
    

    esempio finale

Sono inoltre predefinite le forme lifted (§12.4.8) degli operatori di sottrazione predefiniti non-lifted definiti sopra.

12.11 Operatori shift

Gli operatori << e >> vengono usati per eseguire operazioni di spostamento dei bit.

shift_expression
    : additive_expression
    | shift_expression '<<' additive_expression
    | shift_expression right_shift additive_expression
    ;

Se un operando di un shift_expression ha il tipo in fase di compilazione dynamic, l'espressione viene associata dinamicamente (§12.3.3). In questo caso, il tipo in fase di compilazione dell'espressione è dynamice la risoluzione descritta di seguito avverrà a runtime, usando il tipo di runtime degli operandi che hanno il tipo in fase di compilazione dynamic.

Per un'operazione della forma x << count o x >> count, si applica la risoluzione dell'overload dell'operatore binario (§12.4.5) per selezionare un'implementazione specifica dell'operatore. Gli operandi vengono convertiti nei tipi di parametro dell'operatore selezionato e il tipo del risultato è il tipo restituito dell'operatore.

Quando si dichiara un operatore di overload di shift, il tipo del primo operando deve essere sempre la classe o la struct contenente la dichiarazione dell'operatore e il tipo del secondo operando deve sempre essere int.

Di seguito sono elencati gli operatori di spostamento predefiniti.

  • Sposta a sinistra:

    int operator <<(int x, int count);
    uint operator <<(uint x, int count);
    long operator <<(long x, int count);
    ulong operator <<(ulong x, int count);
    

    L'operatore << sposta x verso sinistra di un numero di bit calcolato come descritto di seguito.

    I bit di ordine elevato non compresi nell'intervallo del tipo di risultato di x vengono eliminati, i bit rimanenti vengono spostati a sinistra e le posizioni dei bit vuoti in ordine basso vengono impostate su zero.

  • Sposta a destra:

    int operator >>(int x, int count);
    uint operator >>(uint x, int count);
    long operator >>(long x, int count);
    ulong operator >>(ulong x, int count);
    

    L'operatore >> sposta x a destra in base a un numero di bit calcolati come descritto di seguito.

    Quando x è di tipo int o long, i bit di ordine basso di x vengono rimossi, i bit rimanenti vengono spostati verso destra e le posizioni di bit vuote dell'ordine elevato vengono impostate su zero se x è non negativo e impostato su uno se x è negativo.

    Quando x è di tipo uint o ulong, i bit di ordine basso di x vengono rimossi, i bit rimanenti vengono spostati a destra e le posizioni dei bit vuoti dell'ordine elevato vengono impostate su zero.

Per gli operatori predefiniti, il numero di bit da spostare viene calcolato come segue:

  • Quando il tipo di x è int o uint, il conteggio degli spostamenti viene determinato dai cinque bit meno significativi di count. In altre parole, il conteggio dei turni viene calcolato da count & 0x1F.
  • Quando il tipo di x è long o ulong, il conteggio di spostamento viene determinato dai sei bit meno significativi di count. In altre parole, il conteggio dei turni viene calcolato da count & 0x3F.

Se il conteggio dei turni risultante è zero, gli operatori di shift restituiscono semplicemente il valore di x.

Le operazioni di spostamento non causano mai overflow e producono gli stessi risultati in contesti controllati e non controllati.

Quando l'operando sinistro dell'operatore è di un tipo integrale con segno, l'operatore esegue uno spostamento aritmetico a destra in cui il valore del bit più significativo (bit segno) dell'operando viene propagato alle posizioni di bit vuote nell'ordine elevato. Quando l'operando sinistro dell'operatore >> è di un tipo integrale senza segno, l'operatore esegue uno spostamento logico a destra , dove le posizioni dei bit vuote più significative vengono sempre impostate su zero. Per eseguire l'operazione opposta a quella dedotta dal tipo di operando, è possibile utilizzare dei cast espliciti.

esempio: se x è una variabile di tipo int, l'operazione unchecked ((int)((uint)x >> y)) esegue uno spostamento logico a destra di x. esempio finale

Anche le forme sollevate (§12.4.8) degli operatori di spostamento predefiniti unlifted definiti in precedenza sono anch'esse predefinite.

12.12 Operatori relazionali e di test dei tipi

12.12.1 Generale

Gli operatori ==, !=, <, >, <=, >=, ise as sono chiamati operatori relazionali e di test dei tipi.

relational_expression
    : shift_expression
    | relational_expression '<' shift_expression
    | relational_expression '>' shift_expression
    | relational_expression '<=' shift_expression
    | relational_expression '>=' shift_expression
    | relational_expression 'is' type
    | relational_expression 'is' pattern
    | relational_expression 'as' type
    ;

equality_expression
    : relational_expression
    | equality_expression '==' relational_expression
    | equality_expression '!=' relational_expression
    ;

Nota: la ricerca dell'operando destro dell'operatore is deve prima essere verificato come tipo , quindi come espressione che può estendersi su più token. Nel caso in cui l'operando sia un'espressione , l'espressione di modello deve avere una precedenza almeno tanto alta quanto shift_expression. nota finale

L'operatore is è descritto in §12.12.12 e l'operatore as è descritto in §12.12.13.

Gli operatori ==, !=, <, >, <= e >= sono operatori di confronto .

Se un default_literal (§12.8.21) viene utilizzato come operando di operatore <, >, <=o >= , si verifica un errore in fase di compilazione. Se un default_literal viene utilizzato come operando di un operatore == o !=, si verifica un errore di compilazione. Se un default_literal viene usato come operando sinistro dell'operatore is o as, si verifica un errore in fase di compilazione.

Se un operando di un operatore di confronto ha il tipo in fase di compilazione dynamic, l'espressione viene associata dinamicamente (§12.3.3). In questo caso, il tipo in fase di compilazione dell'espressione è dynamic, e la risoluzione descritta di seguito verrà eseguita in fase di esecuzione, utilizzando il tipo di runtime di quegli operandi il cui tipo in fase di compilazione è dynamic.

Per un'operazione del modulo x «op» y, dove «op» è un operatore di confronto, la risoluzione dell'overload (§12.4.5) viene applicata per selezionare un'implementazione specifica dell'operatore. Gli operandi vengono convertiti nei tipi di parametro dell'operatore selezionato e il tipo del risultato è il tipo restituito dell'operatore. Se entrambi gli operandi di un equality_expression sono il valore letterale null, la risoluzione dell'overload non viene eseguita e l'espressione restituisce un valore costante di true o false in base al fatto che l'operatore sia == o !=.

Gli operatori di confronto predefiniti sono descritti nelle sottoclause seguenti. Tutti gli operatori di confronto predefiniti restituiscono un risultato di tipo bool, come descritto nella tabella seguente.

operazione Risultato
x == y true se x è uguale a y, false in caso contrario
x != y true se x non è uguale a y, false in caso contrario
x < y true se x è minore di y, false in caso contrario
x > y true se x è maggiore di y, false in caso contrario
x <= y true se x è minore o uguale a y, false in caso contrario
x >= y true se x è maggiore o uguale a y, false in caso contrario

12.12.2 Operatori di confronto integer

Gli operatori di confronto integer predefiniti sono:

bool operator ==(int x, int y);
bool operator ==(uint x, uint y);
bool operator ==(long x, long y);
bool operator ==(ulong x, ulong y);

bool operator !=(int x, int y);
bool operator !=(uint x, uint y);
bool operator !=(long x, long y);
bool operator !=(ulong x, ulong y);

bool operator <(int x, int y);
bool operator <(uint x, uint y);
bool operator <(long x, long y);
bool operator <(ulong x, ulong y);

bool operator >(int x, int y);
bool operator >(uint x, uint y);
bool operator >(long x, long y);
bool operator >(ulong x, ulong y);

bool operator <=(int x, int y);
bool operator <=(uint x, uint y);
bool operator <=(long x, long y);
bool operator <=(ulong x, ulong y);

bool operator >=(int x, int y);
bool operator >=(uint x, uint y);
bool operator >=(long x, long y);
bool operator >=(ulong x, ulong y);

Ognuno di questi operatori confronta i valori numerici dei due operandi integer e restituisce un valore bool che indica se la relazione specifica è true o false.

Lifted (§12.4.8) forme degli operatori di confronto interi predefiniti non sollevati definiti in precedenza sono anch'esse predefinite.

12.12.3 Operatori di confronto a virgola mobile

Gli operatori di confronto a virgola mobile predefiniti sono:

bool operator ==(float x, float y);
bool operator ==(double x, double y);

bool operator !=(float x, float y);
bool operator !=(double x, double y);

bool operator <(float x, float y);
bool operator <(double x, double y);

bool operator >(float x, float y);
bool operator >(double x, double y);

bool operator <=(float x, float y);
bool operator <=(double x, double y);

bool operator >=(float x, float y);
bool operator >=(double x, double y);

Gli operatori confrontano gli operandi in base alle regole dello standard IEC 60559:

Se uno degli operandi è NaN, il risultato è false per tutti gli operatori, ad eccezione di !=, per cui il risultato è true. Per due operandi, x != y produce sempre lo stesso risultato di !(x == y). Tuttavia, quando uno o entrambi gli operandi sono NaN, gli operatori <, >, <=e >=non producono gli stessi risultati della negazione logica dell'operatore opposto.

esempio: se una delle x e y è NaN, x < y è false, ma !(x >= y) è true. esempio finale

Quando nessuno degli operandi è NaN, gli operatori confrontano i valori dei due operandi a virgola mobile rispetto all'ordinamento

–∞ < –max < ... < –min < –0.0 == +0.0 < +min < ... < +max < +∞

dove min e max sono i valori finiti positivi più piccoli e più grandi che possono essere rappresentati nel formato a virgola mobile specificato. Gli effetti rilevanti di questo ordinamento sono:

  • Gli zeri negativi e positivi sono considerati uguali.
  • Un infinito negativo viene considerato minore di tutti gli altri valori, ma uguale a un altro infinito negativo.
  • Un infinito positivo è considerato maggiore di tutti gli altri valori, ma uguale a un altro infinito positivo.

Anche le forme sollevate (§12.4.8) degli operatori di confronto a virgola mobile predefiniti non sollevati definiti sopra sono predefiniti.

12.12.4 Operatori di confronto decimali

Gli operatori di confronto decimali predefiniti sono:

bool operator ==(decimal x, decimal y);
bool operator !=(decimal x, decimal y);
bool operator <(decimal x, decimal y);
bool operator >(decimal x, decimal y);
bool operator <=(decimal x, decimal y);
bool operator >=(decimal x, decimal y);

Ognuno di questi operatori confronta i valori numerici dei due operandi decimali e restituisce un valore bool che indica se la relazione specifica è true o false. Ogni confronto decimale equivale all'uso dell'operatore relazionale o di uguaglianza corrispondente di tipo System.Decimal.

Anche le forme liftate (§12.4.8) degli operatori di confronto decimali predefiniti non liftati definiti in precedenza sono predefinite.

12.12.5 Operatori di uguaglianza booleani

Gli operatori di uguaglianza booleani predefiniti sono:

bool operator ==(bool x, bool y);
bool operator !=(bool x, bool y);

Il risultato di == è true se sia x che y sono true o se sia x che y sono false. In caso contrario, il risultato è false.

Il risultato di != è false se sia x che y sono true o se sia x che y sono false. In caso contrario, il risultato è true. Quando gli operandi sono di tipo bool, l'operatore != produce lo stesso risultato dell'operatore ^.

Lifted (§12.4.8) forme degli operatori di uguaglianza booleani predefiniti non sollevato definiti in precedenza sono anch'esso predefiniti.

12.12.6 Operatori di confronto di enumerazione

Ogni tipo di enumerazione fornisce in modo implicito gli operatori di confronto predefiniti seguenti

bool operator ==(E x, E y);
bool operator !=(E x, E y);

bool operator <(E x, E y);
bool operator >(E x, E y);
bool operator <=(E x, E y);
bool operator >=(E x, E y);

Il risultato della valutazione di x «op» y, dove x e y sono espressioni di un tipo di enumerazione E con un tipo sottostante Ue «op» è uno degli operatori di confronto, equivale esattamente alla valutazione di ((U)x) «op» ((U)y). In altre parole, gli operatori di confronto dei tipi di enumerazione confrontano semplicemente i valori integrali sottostanti dei due operandi.

Le versioni sollevate (§12.4.8) degli operatori di confronto di enumerazione predefiniti, come quelli non sollevati definiti sopra, sono anch'esse predefinite.

12.12.7 Operatori di uguaglianza dei tipi di riferimento

Ogni tipo di classe C fornisce implicitamente i seguenti operatori di uguaglianza dei tipi di riferimento predefiniti:

bool operator ==(C x, C y);
bool operator !=(C x, C y);

a meno che non esistano operatori di uguaglianza predefiniti per C, ad esempio quando C è string o System.Delegate.

Gli operatori restituiscono il risultato del confronto dei due riferimenti per l'uguaglianza o la mancata uguaglianza. operator == restituisce true se e solo se x e y fanno riferimento alla stessa istanza o sono entrambi null, mentre operator != restituisce true se e solo se operator == con gli stessi operandi restituirebbero false.

Oltre alle normali regole di applicabilità (§12.6.4.2), gli operatori di uguaglianza dei tipi di riferimento predefiniti richiedono uno dei seguenti elementi per poter essere applicabili:

  • Entrambi gli operandi sono un valore di un tipo noto come reference_type o il valore letterale null. Inoltre, esiste una conversione di riferimento esplicito o di identità (§10.3.5) da uno degli operandi al tipo dell'altro operando.
  • Un operando è il valore letterale nulle l'altro operando è un valore di tipo T dove T è un parametro di tipo che non è noto per essere un tipo di valore e non ha il vincolo di tipo di valore.
    • Se durante l'esecuzione T è un tipo valore non annullabile, il risultato di == è false e il risultato di != è true.
    • Se durante l'esecuzione T è un tipo di valore nullable, il risultato è calcolato utilizzando la proprietà HasValue dell'operando, come descritto in (§12.12.10).
    • Se, in fase di esecuzione, T è un tipo di riferimento, il risultato è true se l'operando è nulle false in caso contrario.

A meno che una di queste condizioni non sia vera, si verifica un errore di tempo di associazione.

Nota: le implicazioni rilevanti di queste regole sono:

  • È un errore al tempo di associazione usare gli operatori di uguaglianza dei tipi di riferimento predefiniti per confrontare due riferimenti che sono noti per essere diversi durante l'associazione. Ad esempio, se i tipi di tempo di associazione degli operandi sono due tipi di classe, e se nessuno deriva dall'altro, sarebbe impossibile che i due operandi facciano riferimento allo stesso oggetto. Di conseguenza, l'operazione viene considerata un errore in fase di associazione.
  • Gli operatori di uguaglianza dei tipi di riferimento predefiniti non consentono di confrontare gli operandi di tipo valore (tranne quando i parametri di tipo vengono confrontati con null, che viene gestito appositamente).
  • Gli operandi di operatori di uguaglianza dei tipi di riferimento predefiniti non vengono mai boxed. Sarebbe inutile eseguire tali operazioni di boxing, poiché i riferimenti alle istanze boxed appena allocate sarebbero necessariamente diversi da tutti gli altri riferimenti.

Per un'operazione della forma x == y o x != y, se esiste un operator == o operator != applicabile definito dall'utente, le regole di risoluzione degli overload degli operatori (§12.4.5) selezioneranno quell'operatore anziché l'operatore di uguaglianza del tipo di riferimento predefinito. È sempre possibile selezionare l'operatore di uguaglianza del tipo di riferimento predefinito eseguendo il cast esplicito di uno o entrambi gli operandi al tipo object.

nota finale

esempio: l'esempio seguente controlla se un argomento di un tipo di parametro non vincolato è null.

class C<T>
{
   void F(T x)
   {
      if (x == null)
      {
          throw new ArgumentNullException();
      }
      ...
   }
}

Il costrutto x == null è consentito anche se T potrebbe rappresentare un tipo di valore non nullable e il risultato viene semplicemente definito come false quando T è un tipo di valore non nullable.

esempio finale

Per un'operazione della forma x == y o x != y, se esiste un operator == applicabile o operator !=, la risoluzione dell'overload dell'operatore (§12.4.5) selezionerà tale operatore anziché l'operatore di uguaglianza del tipo di riferimento predefinito.

Nota: è sempre possibile selezionare l'operatore di uguaglianza del tipo di riferimento predefinito eseguendo il cast esplicito di entrambi gli operandi al tipo object. nota finale

Esempio: Esempio

class Test
{
    static void Main()
    {
        string s = "Test";
        string t = string.Copy(s);
        Console.WriteLine(s == t);
        Console.WriteLine((object)s == t);
        Console.WriteLine(s == (object)t);
        Console.WriteLine((object)s == (object)t);
    }
}

produce l'output

True
False
False
False

Le variabili s e t fanno riferimento a due istanze di stringa distinte contenenti gli stessi caratteri. Il primo confronto restituisce True perché l'operatore predefinito di uguaglianza di stringhe (§12.12.8) viene selezionato quando entrambi gli operandi sono di tipo string. Tutti i confronti rimanenti producono False perché l'overload di operator == nel tipo string non è applicabile se uno degli operandi ha un tipo di binding-time di object.

Si noti che la tecnica precedente non è significativa per i tipi valore. Esempio

class Test
{
    static void Main()
    {
        int i = 123;
        int j = 123;
        Console.WriteLine((object)i == (object)j);
    }
}

restituisce False perché i cast creano riferimenti a due istanze separate di valori int incapsulati.

esempio finale

12.12.8 Operatori di uguaglianza di stringhe

Gli operatori predefiniti di uguaglianza di stringhe sono:

bool operator ==(string x, string y);
bool operator !=(string x, string y);

Due valori string vengono considerati uguali quando una delle condizioni seguenti è vera:

  • Entrambi i valori sono null.
  • Entrambi i valori sono riferimenti nonnull a istanze di stringa che hanno lunghezze identiche e caratteri identici in ogni posizione.

Gli operatori di uguaglianza di stringhe confrontano i valori stringa anziché i riferimenti stringa. Quando due istanze di stringa separate contengono la stessa sequenza di caratteri, i valori delle stringhe sono uguali, ma i riferimenti sono diversi.

Nota: come descritto in §12.12.7, gli operatori di uguaglianza dei tipi di riferimento possono essere usati per confrontare riferimenti stringa anziché valori stringa. nota finale

12.12.9 Delegare gli operatori di uguaglianza

Gli operatori di uguaglianza dei delegati predefiniti sono:

bool operator ==(System.Delegate x, System.Delegate y);
bool operator !=(System.Delegate x, System.Delegate y);

Due istanze di delegato sono considerate uguali come segue:

  • Se una delle istanze del delegato è null, allora le istanze sono uguali se e solo se entrambe sono null.
  • Se i delegati hanno un tipo di runtime diverso, non sono mai uguali.
  • Se entrambe le istanze del delegato hanno un elenco di chiamate (§20.2), tali istanze sono uguali se e solo se i loro elenchi di chiamate hanno la stessa lunghezza e ogni voce nell'elenco di chiamate di uno è uguale (come definito di seguito) alla voce corrispondente, in ordine, nell'elenco di chiamate dell'altro.

** Le seguenti regole disciplinano l'equivalenza delle voci dell'elenco delle invocazioni:

  • Se due voci dell'elenco chiamate fanno entrambe riferimento allo stesso metodo statico, allora le voci sono uguali.
  • Se due voci dell'elenco chiamate fanno entrambi riferimento allo stesso metodo non statico sullo stesso oggetto di destinazione (come definito dagli operatori di uguaglianza dei riferimenti), le voci sono uguali.
  • Le voci dell'elenco di invocazione generate dalla valutazione di funzioni anonime semanticamente identiche (§12.19) con lo stesso set (possibilmente vuoto) di istanze di variabili esterne acquisite sono consentite (ma non obbligate) ad essere uguali.

Se la risoluzione dell'overload dell'operatore viene risolta in un operatore di uguaglianza delegato e i tipi di data e ora di associazione di entrambi gli operandi sono tipi delegati, come descritto in §20 anziché System.Delegatee non esiste alcuna conversione di identità tra i tipi di operando di tipo binding, si verifica un errore di binding-time.

Nota: questa regola impedisce confronti che non possono mai considerare valori nonnull uguali a causa di riferimenti a istanze di diversi tipi di delegati. nota finale

12.12.10 Operatori di uguaglianza tra i tipi valore nullable e il valore letterale Null

Gli operatori == e != consentono a un operando di assumere il valore di un tipo di valore nullable e all'altro di essere il valore letterale null, anche se non esiste alcun operatore predefinito o definito dall'utente (in forma non sollevata oppure sollevata) per l'operazione.

Per un'operazione di uno dei formulari

x == null    null == x    x != null    null != x

dove x è un'espressione di un tipo di valore nullable, se la risoluzione dell'overload dell'operatore (§12.4.5) non riesce a trovare un operatore applicabile, il risultato viene calcolato utilizzando la proprietà HasValue di x. In particolare, le prime due forme vengono convertite in !x.HasValuee le ultime due forme vengono convertite in x.HasValue.

12.12.11 Operatori di uguaglianza delle tuple

Gli operatori di uguaglianza della tupla vengono applicati in modo pairwise agli elementi degli operandi di tupla in ordine lessicale.

Se ogni operando x e y di un operatore == o != viene classificato come tupla o come valore con un tipo di tupla (§8.3.11), l'operatore è un operatore di uguaglianza di tupla .

Se un operando e viene classificato come tupla, gli elementi e1...en sono ottenuti valutando le espressioni degli elementi dell'espressione della tupla. In caso contrario, se e è un valore di un tipo di tupla, gli elementi devono essere t.Item1...t.Itemn dove t è il risultato della valutazione di e.

Gli operandi x e y di un operatore di uguaglianza della tupla devono avere la stessa arità, altrimenti si verifica un errore di compilazione. Per ogni coppia di elementi xi e yi, lo stesso operatore di uguaglianza deve essere applicato e restituisce un risultato di tipo bool, dynamic, un tipo con una conversione implicita in boolo un tipo che definisce gli operatori true e false.

L'operatore di uguaglianza della tupla x == y viene considerato nel seguente modo:

  • Viene valutato l'operando a sinistra x.
  • Viene valutato l'operando destro y.
  • Per ogni coppia di elementi xi e yi in ordine lessicale:
    • L'operatore xi == yi viene valutato e il risultato del tipo bool viene ottenuto nel modo seguente:
      • Se il confronto ha restituito un bool, questo è il risultato.
      • In caso contrario, se il confronto ha restituito un dynamic, l'operatore false viene richiamato dinamicamente su di esso e il valore bool risultante viene negato con l'operatore di negazione logico (!).
      • In caso contrario, se il tipo di confronto dispone di una conversione implicita a bool, tale conversione viene applicata.
      • In caso contrario, se il tipo di confronto ha un operatore false, tale operatore viene richiamato e il valore bool risultante viene negato con l'operatore di negazione logico (!).
    • Se il risultato di bool è false, non viene eseguita alcuna ulteriore valutazione e il risultato dell'operatore di uguaglianza della tupla è false.
  • Se i confronti fra tutti gli elementi risultano in true, l'operatore di uguaglianza della tupla dà come risultato true.

L'operatore di uguaglianza della tupla x != y viene considerato nel seguente modo:

  • Viene valutato l'operando a sinistra x.
  • Viene valutato l'operando destro y.
  • Per ogni coppia di elementi xi e yi in ordine lessicale:
    • L'operatore xi != yi viene valutato e il risultato del tipo bool viene ottenuto nel modo seguente:
      • Se il confronto ha restituito un bool, questo è il risultato.
      • In caso contrario, se il confronto ha restituito un dynamic, l'operatore true viene richiamato dinamicamente su di esso e il valore bool risultante è il risultato.
      • In caso contrario, se il tipo di confronto dispone di una conversione implicita a bool, tale conversione viene applicata.
      • In caso contrario, se il tipo di confronto ha un operatore true, tale operatore viene richiamato e il valore bool risultante è il risultato.
    • Se il risultato di bool è true, non viene eseguita alcuna ulteriore valutazione e il risultato dell'operatore di uguaglianza della tupla è true.
  • Se i confronti fra tutti gli elementi risultano in false, l'operatore di uguaglianza della tupla dà come risultato false.

12.12.12 L'operatore is

Esistono due forme dell'operatore is. Uno è l'operatore di tipo di tipo, che ha un tipo a destra. L'altro è l'operatore is-pattern, che ha un modello sulla parte destra.

12.12.12.1 Operatore di tipo is

L'operatore di tipo is-type viene usato per verificare se il tipo di runtime di un oggetto è compatibile con un determinato tipo. Il controllo viene eseguito in fase di esecuzione. Il risultato dell'operazione E is T, dove E è un'espressione e T è un tipo diverso da dynamic, è un valore booleano che indica se E è diverso da null e se può essere convertito in modo corretto nel tipo T tramite una conversione tramite riferimento, una conversione di boxing, una conversione di unboxing, una conversione di wrapping o una conversione di unwrapping.

L'operazione viene valutata come segue:

  1. Se E è una funzione o un gruppo di metodi anonimi, si verifica un errore in fase di compilazione
  2. Se E è il valore letterale null o se il valore di E è null, il risultato è false.
  3. Altrimenti:
  4. Sia R il tipo di runtime di E.
  5. È possibile D derivare da R come indicato di seguito:
  6. Se R è un tipo di valore nullo, D è il tipo sottostante di R.
  7. In caso contrario, D è R.
  8. Il risultato dipende da D e T come indicato di seguito:
  9. Se T è un tipo riferimento, il risultato viene true se:
    • esiste una conversione di identità tra D e T,
    • D è un tipo riferimento e una conversione implicita dei riferimenti da D a T esiste oppure
    • O: D è un tipo di valore ed esiste una conversione boxing da D a T.
      Oppure: D è un tipo valore e T è un tipo di interfaccia implementato da D.
  10. Se T è un tipo valore nullable, il risultato è true se D è il tipo sottostante di T.
  11. Se T è un tipo di valore non a valore nullo, il risultato è true se D e T sono dello stesso tipo.
  12. In caso contrario, il risultato è false.

Le conversioni definite dall'utente non vengono considerate dall'operatore is.

Nota: poiché l'operatore is viene valutato in fase di esecuzione, tutti gli argomenti di tipo sono stati sostituiti e non sono presenti tipi aperti (§8.4.3) da considerare. nota finale

Nota: l'operatore is può essere compreso in termini di tipi e conversioni in fase di compilazione come indicato di seguito, dove C è il tipo in fase di compilazione di E:

  • Se il tipo in fase di compilazione di e è lo stesso di T, o se una conversione di riferimento implicita (§10.2.8), conversione di boxing (§10.2.9), conversione di wrapping (§10.6) o una conversione esplicita di annullamento del wrapping (§10.6) esiste dal tipo in fase di compilazione di E a T:
    • Se C è di un tipo valore non annullabile, il risultato dell'operazione è true.
    • In caso contrario, il risultato dell'operazione equivale a valutare E != null.
  • In caso contrario, se esiste una conversione esplicita dei riferimenti (§10.3.5) o una conversione unboxing (§10.3.7) da C a T, oppure se C o T è un tipo aperto (§8.4.3), i controlli a runtime dovranno essere eseguiti come sopra.
  • In caso contrario, non è possibile eseguire riferimento, conversione boxing, wrapping o unwrapping di E nel tipo T e il risultato dell'operazione è false. Un compilatore può implementare ottimizzazioni in base al tipo al momento della compilazione.

nota finale

12.12.12.2 Operatore is-pattern

L'operatore is-pattern serve per verificare se il valore calcolato da un'espressione corrisponde a un determinato pattern (§11). Il controllo viene eseguito in fase di esecuzione. Il risultato dell'operatore is-pattern è true se il valore corrisponde allo schema; in caso contrario, è false.

Per un'espressione del tipo E is P, dove E è un'espressione relazionale di tipo T e P è un criterio, si verifica un errore in fase di compilazione se si verifica una delle seguenti condizioni:

  • E non designa un valore o non dispone di un tipo.
  • Il modello P non è applicabile (§11.2) al tipo T.

Operatore "as"

L'operatore as viene usato per convertire in modo esplicito un valore in un determinato tipo riferimento o in un tipo valore nullable. A differenza di un'espressione cast (§12.9.7), l'operatore as non genera mai un'eccezione. Se invece la conversione indicata non è possibile, il valore risultante viene null.

In un'operazione del modulo E as T, E deve essere un'espressione e T deve essere un tipo riferimento, un parametro di tipo noto come tipo riferimento o un tipo valore nullable. Inoltre, almeno uno dei seguenti deve essere vero, altrimenti si verificherà un errore di compilazione.

  • Un'identità (§10.2.2), nullable implicito (§10.2.6), riferimento implicito (§10.2.8), boxing (§10.2.9), esplicito nullable (§10.3.4), riferimento esplicito (§10.3.5) o wrapping (§8.3.12) la conversione esiste da E a T.
  • Il tipo di E o T è un tipo aperto.
  • E è il valore letterale null.

Se il tipo di E al momento della compilazione non è dynamic, allora l'operazione E as T produce lo stesso risultato di

E is T ? (T)(E) : (T)null

ad eccezione del fatto che E viene valutato una sola volta. È possibile prevedere che un compilatore ottimizzi E as T per eseguire al massimo un controllo del tipo di runtime anziché i due controlli del tipo di runtime impliciti nell'espansione precedente.

Se il tipo di E in fase di compilazione è dynamic, a differenza dell'operatore cast, l'operatore as non è associato dinamicamente (§12.3.3). Di conseguenza, l'espansione in questo caso è:

E is T ? (T)(object)(E) : (T)null

Si noti che alcune conversioni, ad esempio le conversioni definite dall'utente, non sono possibili con l'operatore as e devono invece essere eseguite usando espressioni cast.

Esempio: nell'esempio

class X
{
    public string F(object o)
    {
        return o as string;  // OK, string is a reference type
    }

    public T G<T>(object o)
        where T : Attribute
    {
        return o as T;       // Ok, T has a class constraint
    }

    public U H<U>(object o)
    {
        return o as U;       // Error, U is unconstrained
    }
}

Il parametro di tipo T di G è noto come tipo riferimento, perché ha il vincolo di classe. Il parametro di tipo U di H non è tuttavia; pertanto l'uso dell'operatore as in H non è consentito.

esempio finale

12.13 Operatori logici

12.13.1 Generale

Gli operatori &,^e | vengono chiamati operatori logici.

and_expression
    : equality_expression
    | and_expression '&' equality_expression
    ;

exclusive_or_expression
    : and_expression
    | exclusive_or_expression '^' and_expression
    ;

inclusive_or_expression
    : exclusive_or_expression
    | inclusive_or_expression '|' exclusive_or_expression
    ;

Se un operando di un operatore logico ha il tipo in fase di compilazione dynamic, l'espressione viene associata dinamicamente (§12.3.3). In questo caso, il tipo in fase di compilazione dell'espressione è dynamic, e la risoluzione descritta di seguito verrà eseguita in fase di esecuzione, utilizzando il tipo di runtime di quegli operandi il cui tipo in fase di compilazione è dynamic.

Per un'operazione del modulo x «op» y, dove «op» è uno degli operatori logici, viene applicata la risoluzione dell'overload (§12.4.5) per selezionare un'implementazione specifica dell'operatore. Gli operandi vengono convertiti nei tipi di parametro dell'operatore selezionato e il tipo del risultato è il tipo restituito dell'operatore.

Gli operatori logici predefiniti sono descritti nelle sottoclause seguenti.

12.13.2 Operatori logici Integer

Gli operatori logici integer predefiniti sono:

int operator &(int x, int y);
uint operator &(uint x, uint y);
long operator &(long x, long y);
ulong operator &(ulong x, ulong y);

int operator |(int x, int y);
uint operator |(uint x, uint y);
long operator |(long x, long y);
ulong operator |(ulong x, ulong y);

int operator ^(int x, int y);
uint operator ^(uint x, uint y);
long operator ^(long x, long y);
ulong operator ^(ulong x, ulong y);

L'operatore & calcola l'AND logico bit per bit dei due operandi, l'operatore | calcola l'OR logico bit per bit dei due operandi e l'operatore ^ calcola l'OR esclusivo logico bit per bit dei due operandi. Non si possono verificare overflow da queste operazioni.

Sollevate (§12.4.8) forme degli operatori logici interi predefiniti non sollevati definiti in precedenza sono anch'esse predefinite.

12.13.3 Operatori logici di enumerazione

Ogni tipo di enumerazione E in modo implicito fornisce gli operatori logici predefiniti seguenti:

E operator &(E x, E y);
E operator |(E x, E y);
E operator ^(E x, E y);

Il risultato della valutazione di x «op» y, dove x e y sono espressioni di un tipo di enumerazione E con un tipo sottostante Ue «op» è uno degli operatori logici, è esattamente uguale alla valutazione di (E)((U)x «op» (U)y). In altre parole, gli operatori logici di tipo enumerazione eseguono semplicemente l'operazione logica sul tipo sottostante dei due operandi.

Anche le forme sollevate (§12.4.8) degli operatori logici di enumerazione predefiniti non sollevati, definiti in precedenza, sono predefinite.

12.13.4 Operatori logici booleani

Gli operatori logici booleani predefiniti sono:

bool operator &(bool x, bool y);
bool operator |(bool x, bool y);
bool operator ^(bool x, bool y);

Il risultato di x & y è true se x e y sono true. In caso contrario, il risultato è false.

Il risultato di x | y è true se x o y è true. In caso contrario, il risultato è false.

Il risultato di x ^ y è true se x è true e y è falseo x è false e y è true. In caso contrario, il risultato è false. Quando gli operandi sono di tipo bool, l'operatore ^ calcola lo stesso risultato dell'operatore !=.

12.13.5 & booleani nullable e | Operatori

Il tipo booleano nullable bool? può rappresentare tre valori, true, falsee null.

Analogamente agli altri operatori binari, sono predefinite anche le forme sollevate degli operatori logici & e | (§12.13.4)

bool? operator &(bool? x, bool? y);
bool? operator |(bool? x, bool? y);

La semantica degli operatori lifted & e | è definita dalla tabella seguente:

x y x & y x \| y
true true true true
true false false true
true null null true
false true false true
false false false false
false null false null
null true null true
null false false null
null null null null

Nota: il tipo di bool? è concettualmente simile al tipo a tre valori usato per le espressioni booleane in SQL. La tabella precedente segue le stesse semantiche di SQL, mentre l'applicazione delle regole di §12.4.8 agli operatori & e | non lo farebbe. Le regole di §12.4.8 forniscono già una semantica simile a SQL per l'operatore ^ innalzato. nota finale

12.14 Operatori logici condizionali

12.14.1 Generale

Gli operatori && e || vengono chiamati operatori logici condizionali. Sono anche chiamati gli operatori logici "a corto circuito".

conditional_and_expression
    : inclusive_or_expression
    | conditional_and_expression '&&' inclusive_or_expression
    ;

conditional_or_expression
    : conditional_and_expression
    | conditional_or_expression '||' conditional_and_expression
    ;

Gli operatori && e || sono versioni condizionali degli operatori & e |:

  • L'operazione x && y corrisponde all'operazione x & y, ad eccezione del fatto che y viene valutata solo se x non è false.
  • L'operazione x || y corrisponde all'operazione x | y, ad eccezione del fatto che y viene valutata solo se x non è true.

Nota: il motivo per cui il corto circuito usa le condizioni 'not true' e 'not false' è consentire agli operatori condizionali definiti dall'utente di definire quando si applica il corto circuito. I tipi definiti dall'utente possono trovarsi in uno stato in cui operator true restituisce false e operator false restituisce false. In questi casi, né &&|| andrebbe in corto circuito. nota finale

Se un operando di un operatore logico condizionale ha il tipo in fase di compilazione dynamic, l'espressione viene associata dinamicamente (§12.3.3). In questo caso, il tipo in fase di compilazione dell'espressione è dynamic, e la risoluzione descritta di seguito verrà eseguita in fase di esecuzione, utilizzando il tipo di runtime di quegli operandi il cui tipo in fase di compilazione è dynamic.

Un'operazione della forma x && y o x || y è elaborata applicando la risoluzione del sovraccarico (§12.4.5) come se l'operazione fosse scritta x & y o x | y. Allora

  • Se la risoluzione dell'overload non riesce a trovare un unico miglior operatore o se la risoluzione dell'overload seleziona uno degli operatori logici integer predefiniti o operatori logici booleani nullabili (§12.13.5), si verifica un errore di binding-time.
  • In caso contrario, se l'operatore selezionato è uno degli operatori logici booleani predefiniti (§12.13.4), l'operazione viene elaborata come descritto in §12.14.2.
  • In caso contrario, l'operatore selezionato è un operatore definito dall'utente e l'operazione viene elaborata come descritto in §12.14.3.

Non è possibile eseguire direttamente l'overload degli operatori logici condizionali. Tuttavia, poiché gli operatori logici condizionali vengono valutati in termini di operatori logici regolari, gli overload degli operatori logici regolari sono, con determinate restrizioni, considerati anche overload degli operatori logici condizionali. Questo è descritto più avanti in §12.14.3.

12.14.2 Operatori logici condizionali booleani

Quando gli operandi di && o || sono di tipo boolo quando gli operandi sono di tipi che non definiscono un operator & applicabile o operator |, ma definiscono conversioni implicite in bool, l'operazione viene elaborata come segue:

  • L'operazione x && y viene valutata come x ? y : false. In altre parole, x viene prima valutato e convertito nel tipo bool. Quindi, se x è true, y viene valutato e convertito in tipo boole questo diventa il risultato dell'operazione. In caso contrario, il risultato dell'operazione è false.
  • L'operazione x || y viene valutata come x ? true : y. In altre parole, x viene prima valutato e convertito nel tipo bool. Quindi, se x è true, il risultato dell'operazione è true. In caso contrario, y viene valutato e convertito nel tipo boole questo diventa il risultato dell'operazione.

12.14.3 Operatori logici condizionali definiti dall'utente

Quando gli operandi di && o || sono di tipi che dichiarano un operator & definito dall'utente applicabile o operator |, entrambi gli elementi seguenti devono essere veri, dove T è il tipo in cui viene dichiarato l'operatore selezionato.

  • Il tipo restituito e il tipo di ogni parametro dell'operatore selezionato devono essere T. In altre parole, l'operatore calcola l'operatore AND logico o l'OR logico di due operandi di tipo Te restituisce un risultato di tipo T.
  • T deve contenere dichiarazioni di operator true e operator false.

Se uno di questi requisiti non è soddisfatto, si verifica un errore di binding-time. In caso contrario, l'operazione di && o || viene valutata combinando l'operator true definito dall'utente o operator false con l'operatore definito dall'utente selezionato:

  • L'operazione x && y viene valutata come T.false(x) ? x : T.&(x, y), dove T.false(x) è una chiamata del operator false dichiarato in Te T.&(x, y) è una chiamata del operator &selezionato. In altre parole, x viene valutato per primo e operator false viene richiamato sul risultato per determinare se x è sicuramente falso. Se quindi x è decisamente falso, il risultato dell'operazione è il valore precedentemente calcolato per x. In caso contrario, y viene valutato e il operator & selezionato viene richiamato sul valore calcolato in precedenza per x e il valore calcolato per y per produrre il risultato dell'operazione.
  • L'operazione x || y viene valutata come T.true(x) ? x : T.|(x, y), dove T.true(x) è una chiamata del operator true dichiarato in Te T.|(x, y) è una chiamata del operator |selezionato. In altre parole, x viene prima valutato e successivamente operator true viene applicato al risultato per determinare se x è sicuramente vero. Quindi, se x è sicuramente true, il risultato dell'operazione è il valore calcolato in precedenza per x. In caso contrario, y viene valutato e il operator | selezionato viene richiamato sul valore calcolato in precedenza per x e il valore calcolato per y per produrre il risultato dell'operazione.

In entrambe queste operazioni, l'espressione specificata da x viene valutata una sola volta, mentre l'espressione specificata da y non viene valutata o viene valutata esattamente una volta.

12.15 Operatore di coalescenza null

L'operatore ?? viene chiamato operatore di coalescenza di Null.

null_coalescing_expression
    : conditional_or_expression
    | conditional_or_expression '??' null_coalescing_expression
    | throw_expression
    ;

In un'espressione di coalescenza di null nella forma di a ?? b, se a non ènull, il risultato è a; in caso contrario, il risultato è b. L'operazione valuta b solo se a è null.

L'operatore di coalescenza null è associativo a destra, ovvero le operazioni vengono raggruppate da destra a sinistra.

Esempio: Un'espressione della forma a ?? b ?? c viene valutata come a ?? (b ?? c). In termini generali, un'espressione del formato E1 ?? E2 ?? ... ?? EN restituisce il primo valore degli operandi che non ènull, oppure null nel caso in cui tutti gli operandi siano null. esempio finale

Il tipo dell'espressione a ?? b dipende dalle conversioni implicite disponibili negli operandi. In ordine di preferenza, il tipo di a ?? b è A₀, Ao B, dove A è il tipo di a (purché a abbia un tipo), B è il tipo di b(a condizione che b abbia un tipo) e A₀ è il tipo sottostante di A se A è un tipo di valore nullable, o A altrimenti. In particolare, a ?? b viene elaborato come segue:

  • Se A esiste e non è un tipo di valore nullable o un tipo di riferimento, genera un errore in fase di compilazione.
  • In caso contrario, se A esiste e b è un'espressione dinamica, il tipo di risultato è dynamic. In fase di esecuzione, a viene valutata per prima. Se a non è null, a viene convertito in dynamice questo diventa il risultato. In caso contrario, b viene valutato e questo diventa il risultato.
  • In caso contrario, se A esiste ed è un tipo di valore nullable ed esiste una conversione implicita da b a A₀, il tipo di risultato è A₀. In fase di esecuzione, a viene valutata per prima. Se a non è null, a viene decomplicato per digitare A₀e questo diventa il risultato. In caso contrario, b viene valutato e convertito nel tipo A₀e questo diventa il risultato.
  • In caso contrario, se A esiste e esiste una conversione implicita da b a A, il tipo di risultato è A. In fase di esecuzione, "a" viene valutata per prima. Se a non è null, a diventa il risultato. In caso contrario, b viene valutato e convertito nel tipo Ae questo diventa il risultato.
  • Altrimenti, se A esiste ed è un tipo di valore nullable, b ha un tipo B ed esiste una conversione implicita da A₀ a B, il tipo di risultato è B. In fase di esecuzione, a viene valutata per prima. Se a non è null, a viene decomposto nel tipo A₀ e convertito nel tipo B, e questo diventa il risultato. In caso contrario, b viene valutato e diventa il risultato.
  • In caso contrario, se b ha un tipo B e esiste una conversione implicita da a a B, il tipo di risultato è B. In fase di esecuzione, a viene valutata per prima. Se a non è null, a viene convertito nel tipo Be questo diventa il risultato. In caso contrario, b viene valutato e diventa il risultato.

In caso contrario, a e b sono incompatibili e si verifica un errore a in fase di compilazione.

12.16 Operatore di espressione throw

throw_expression
    : 'throw' null_coalescing_expression
    ;

Un throw_expression genera il valore generato valutando il null_coalescing_expression. 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 lanciato. Il comportamento in fase di esecuzione della valutazione di un'espressione throw è uguale a quello specificato per un'istruzione throw (§13.10.6).

Un throw_expression non ha alcun tipo. Un throw_expression è convertibile in ogni tipo tramite una conversione implicita di throw.

Un'espressione throw deve verificarsi solo nei seguenti contesti sintattici.

  • Come secondo o terzo operando di un operatore condizionale ternario (?:).
  • Come secondo operando di un operatore di coalescenza null (??).
  • Come corpo di un'espressione lambda o di un membro con corpo a espressione.

12.17 Espressioni di dichiarazione

Un'espressione di dichiarazione dichiara una variabile locale.

declaration_expression
    : local_variable_type identifier
    ;

local_variable_type
    : type
    | 'var'
    ;

Il simple_name_ viene considerato anche un'espressione di dichiarazione se la ricerca di nomi semplici non ha trovato una dichiarazione associata (§12.8.4). Se usato come espressione di dichiarazione, _ viene chiamato semplice discard. È semanticamente equivalente a var _, ma è consentito in più posizioni.

Un'espressione di dichiarazione si verifica solo nei contesti sintattici seguenti:

  • Come outargument_value in un elenco_argomenti.
  • Come scarto semplice _ che comprende il lato sinistro di un'assegnazione semplice (§12.21.2).
  • Come tuple_element in uno o più tuple_expressionannidati in modo ricorsivo, quello più esterno costituisce il lato sinistro di un'assegnazione destrutturante. Una deconstruction_expression dà origine a espressioni dichiarative in questa posizione, anche se queste non sono effettivamente presenti sintatticamente.

Nota: ciò significa che un'espressione di dichiarazione non può essere racchiusa tra parentesi. nota finale

È un errore che una variabile a tipo implicito dichiarata con un declaration_expression venga referenziata all'interno di argument_list in cui è dichiarata.

Si tratta di un errore per una variabile dichiarata con un declaration_expression a cui fare riferimento all'interno dell'assegnazione di decostruzione in cui si verifica.

Espressione di dichiarazione che è un semplice discard o dove il tipo delle variabili locali è l'identificatore var viene classificato come variabile tipizzata implicitamente . L'espressione non ha alcun tipo e il tipo della variabile locale viene dedotto in base al contesto sintattico come indicato di seguito:

  • In un argument_list il tipo dedotto della variabile è il tipo dichiarato del parametro corrispondente.
  • Come il lato sinistro di un'assegnazione semplice, il tipo dedotto della variabile è il tipo del lato destro dell'assegnazione.
  • In un'espressione di tuple_expression che si trova sul lato sinistro di un'assegnazione semplice, il tipo dedotto della variabile è il tipo dell'elemento corrispondente della tupla sul lato destro dell'assegnazione (dopo la decostruzione).

In caso contrario, l'espressione di dichiarazione viene classificata come variabile tipizzata in modo esplicito e il tipo dell'espressione, così come la variabile dichiarata, devono essere quelli dati dal local_variable_type.

Un'espressione di dichiarazione con l'identificatore _ è un'eccezione (§9.2.9.2) e non introduce un nome per la variabile. Un'espressione di dichiarazione con un identificatore diverso da _ introduce quel nome nello spazio di dichiarazione delle variabili locali più vicino (§7.3).

esempio:

string M(out int i, string s, out bool b) { ... }

var s1 = M(out int i1, "One", out var b1);
Console.WriteLine($"{i1}, {b1}, {s1}");
// Error: i2 referenced within declaring argument list
var s2 = M(out var i2, M(out i2, "Two", out bool b2), out b2);
var s3 = M(out int _, "Three", out var _);

La dichiarazione di s1 mostra sia dichiarazioni espresse in modo esplicito che implicito. Il tipo dedotto di b1 è bool perché è il tipo del parametro di output corrispondente in M1. Il successivo WriteLine è in grado di accedere a i1 e b1, che sono stati introdotti nell'ambito circostante.

La dichiarazione di s2 mostra un tentativo di utilizzare i2 nella chiamata annidata a M, il che non è consentito perché il riferimento si trova all'interno dell'elenco degli argomenti in cui i2 è stato dichiarato. D'altra parte, il riferimento a b2 nell'argomento finale è consentito, perché si verifica dopo la fine dell'elenco di argomenti annidati in cui è stato dichiarato b2.

La dichiarazione di s3 mostra l'uso di espressioni di dichiarazione tipizzata in modo implicito e esplicito che vengono eliminate. Poiché i discard non dichiarano una variabile denominata, sono consentite più occorrenze dell'identificatore _.

(int i1, int _, (var i2, var _), _) = (1, 2, (3, 4), 5);

In questo esempio viene illustrato l'uso di espressioni di dichiarazione tipizzata in modo implicito e esplicito sia per le variabili che per le eliminazioni in un'assegnazione di decostruzione. Il simple_name_ equivale a var _ quando non viene trovata alcuna dichiarazione di _.

void M1(out int i) { ... }

void M2(string _)
{
    M1(out _);      // Error: `_` is a string
    M1(out var _);
}

In questo esempio viene illustrato l'uso di var _ per fornire uno scarto tipizzato in modo implicito quando _ non è disponibile, poiché designa una variabile nell'ambito circostante.

esempio finale

Operatore condizionale 12.18

L'operatore ?: viene chiamato operatore condizionale. A volte viene anche chiamato operatore ternario.

conditional_expression
    : null_coalescing_expression
    | null_coalescing_expression '?' expression ':' expression
    | null_coalescing_expression '?' 'ref' variable_reference ':'
      'ref' variable_reference
    ;

Un'espressione throw (§12.16) non è consentita in un operatore condizionale se ref è presente.

Un'espressione condizionale del form b ? x : y valuta innanzitutto la condizione b. Quindi, se b viene true, x viene valutato e diventa il risultato dell'operazione. In caso contrario, y viene valutato e diventa il risultato dell'operazione. Un'espressione condizionale non valuta mai sia x che y.

L'operatore condizionale è associativo a destra, ovvero le operazioni vengono raggruppate da destra a sinistra.

Esempio: Un'espressione della forma a ? b : c ? d : e viene valutata come a ? b : (c ? d : e). esempio finale

Il primo operando dell'operatore ?: deve essere un'espressione che può essere convertita in modo implicito in boolo un'espressione di un tipo che implementa operator true. Se nessuno di questi requisiti è soddisfatto, si verifica un errore in fase di compilazione.

Se ref è presente:

  • Esiste una conversione di identità tra i tipi dei due variable_referencee il tipo del risultato può essere di entrambi i tipi. Se uno dei due tipi è dynamic, l'inferenza del tipo preferisce dynamic (§8.7). Se uno dei due tipi è un tipo di tupla (§8.3.11), l'inferenza del tipo include i nomi degli elementi quando i nomi degli elementi nella stessa posizione ordinale corrispondono in entrambe le tuple.
  • Il risultato è un riferimento a variabile, scrivibile se entrambe le variable_referencesono scrivibili.

Nota: quando è presente ref, il conditional_expression restituisce un riferimento a una variabile, che può essere assegnato a una variabile di riferimento usando l'operatore = ref o passato come parametro reference/input/output. nota finale

Se ref non è presente, il secondo e il terzo operando, x e y, dell'operatore ?: controllano il tipo dell'espressione condizionale:

  • Se x ha tipo X e y ha tipo Y,
    • Se esiste una conversione di identità tra X e Y, il risultato è il tipo comune migliore di un set di espressioni (§12.6.3.15). Se uno dei due tipi è dynamic, l'inferenza del tipo preferisce dynamic (§8.7). Se uno dei due tipi è un tipo di tupla (§8.3.11), l'inferenza del tipo include i nomi degli elementi quando i nomi degli elementi nella stessa posizione ordinale corrispondono in entrambe le tuple.
    • In caso contrario, se esiste una conversione implicita (§10.2) da X a Y, ma non da Y a X, Y è il tipo dell'espressione condizionale.
    • In caso contrario, se esiste una conversione implicita dell'enumerazione (§10.2.4) da X a Y, Y è il tipo dell'espressione condizionale.
    • In caso contrario, se esiste una conversione implicita dell'enumerazione (§10.2.4) da Y a X, X è il tipo dell'espressione condizionale.
    • In caso contrario, se esiste una conversione implicita (§10.2) da Y a X, ma non da X a Y, X è il tipo dell'espressione condizionale.
    • In caso contrario, non è possibile determinare alcun tipo di espressione e si verifica un errore in fase di compilazione.
  • Se solo uno dei x e y ha un tipo e sia x che y sono convertibili in modo implicito in tale tipo, questo è il tipo dell'espressione condizionale.
  • In caso contrario, non è possibile determinare alcun tipo di espressione e si verifica un errore in fase di compilazione.

L'elaborazione in fase di esecuzione di un'espressione condizionale ref del modulo b ? ref x : ref y è costituita dai passaggi seguenti:

  • Prima di tutto, b viene valutato e viene determinato il valore bool di b:
    • Se esiste una conversione implicita dal tipo di b a bool, questa conversione implicita viene eseguita per produrre un valore bool.
    • In caso contrario, il operator true definito dal tipo di b viene richiamato per produrre un valore bool.
  • Se il valore bool prodotto dal passaggio precedente è true, x viene valutato e il riferimento alla variabile risultante diventa il risultato dell'espressione condizionale.
  • In caso contrario, y viene valutato e il riferimento alla variabile risultante diventa il risultato dell'espressione condizionale.

L'elaborazione in fase di esecuzione di un'espressione condizionale del modulo b ? x : y è costituita dai passaggi seguenti:

  • Prima di tutto, b viene valutato e viene determinato il valore bool di b:
    • Se esiste una conversione implicita dal tipo di b a bool, questa conversione implicita viene eseguita per produrre un valore bool.
    • In caso contrario, il operator true definito dal tipo di b viene richiamato per produrre un valore bool.
  • Se il valore bool prodotto dal passaggio precedente è true, x viene valutato e convertito nel tipo dell'espressione condizionale e questo diventa il risultato dell'espressione condizionale.
  • In caso contrario, y viene valutato e convertito nel tipo dell'espressione condizionale e questo diventa il risultato dell'espressione condizionale.

12.19 Espressioni di funzione anonime

12.19.1 Generale

Una funzione anonima è un'espressione che rappresenta una definizione di metodo "in linea". Una funzione anonima non ha un valore o un tipo in e di se stesso, ma è convertibile in un delegato compatibile o in un tipo di albero delle espressioni. La valutazione di una conversione di funzione anonima dipende dal tipo di destinazione della conversione: se è un tipo delegato, la conversione restituisce un valore delegato che fa riferimento al metodo definito dalla funzione anonima. Se si tratta di un tipo di albero delle espressioni, la conversione restituisce un albero delle espressioni che rappresenta la struttura del metodo in quanto struttura di un oggetto.

Nota: per motivi storici, esistono due tipi sintattici di funzioni anonime, ovvero lambda_expressione anonymous_method_expression. Per quasi tutti gli scopi, lambda_expressions sono più concisi ed espressivi di anonymous_method_expressions, che rimangono nel linguaggio per la compatibilità con le versioni precedenti. nota finale

lambda_expression
    : 'async'? anonymous_function_signature '=>' anonymous_function_body
    ;

anonymous_method_expression
    : 'async'? 'delegate' explicit_anonymous_function_signature? block
    ;

anonymous_function_signature
    : explicit_anonymous_function_signature
    | implicit_anonymous_function_signature
    ;

explicit_anonymous_function_signature
    : '(' explicit_anonymous_function_parameter_list? ')'
    ;

explicit_anonymous_function_parameter_list
    : explicit_anonymous_function_parameter
      (',' explicit_anonymous_function_parameter)*
    ;

explicit_anonymous_function_parameter
    : anonymous_function_parameter_modifier? type identifier
    ;

anonymous_function_parameter_modifier
    : 'ref'
    | 'out'
    | 'in'
    ;

implicit_anonymous_function_signature
    : '(' implicit_anonymous_function_parameter_list? ')'
    | implicit_anonymous_function_parameter
    ;

implicit_anonymous_function_parameter_list
    : implicit_anonymous_function_parameter
      (',' implicit_anonymous_function_parameter)*
    ;

implicit_anonymous_function_parameter
    : identifier
    ;

anonymous_function_body
    : null_conditional_invocation_expression
    | expression
    | 'ref' variable_reference
    | block
    ;

Quando si riconosce un anonymous_function_body, se sia il null_conditional_invocation_expression che l'espressione alternativa sono applicabili, si deve scegliere il primo.

Nota: La sovrapposizione e la priorità tra le alternative qui sono esclusivamente per praticità descrittiva; le regole grammaticali potrebbero essere elaborate per rimuovere la sovrapposizione. ANTLR e altri sistemi grammaticali adottano la stessa praticità e quindi anonymous_function_body ha automaticamente la semantica specificata. nota finale

Nota: se considerato come espressione , una forma sintattica come x?.M() è un errore se il tipo di risultato di M è void (§12.8.13). Tuttavia, se viene trattato come null_conditional_invocation_expression, il tipo di risultato può essere void. nota finale

Esempio: Il tipo di risultato di List<T>.Reverse è void. Nel codice seguente, il corpo dell'espressione anonima è un null_conditional_invocation_expression, quindi non è un errore.

Action<List<int>> a = x => x?.Reverse();

esempio finale

L'operatore => ha la stessa precedenza dell'assegnazione (=) ed è associativa a destra.

Una funzione anonima con il modificatore async è una funzione asincrona e segue le regole descritte in §15.15.

I parametri di una funzione anonima nella forma di lambda_expression possono essere tipizzati in modo esplicito o implicito. In un elenco di parametri tipizzato in modo esplicito, il tipo di ogni parametro viene dichiarato in modo esplicito. In un elenco di parametri tipizzato in modo implicito, i tipi dei parametri vengono dedotti dal contesto in cui si verifica la funzione anonima, in particolare quando la funzione anonima viene convertita in un tipo delegato o un tipo di albero delle espressioni compatibile, tale tipo fornisce i tipi di parametro (§10.7).

In un lambda_expression con un singolo parametro tipizzato in modo implicito, le parentesi possono essere omesse dall'elenco di parametri. In altre parole, una funzione anonima del form

( «param» ) => «expr»

può essere abbreviato in

«param» => «expr»

L'elenco di parametri di una funzione anonima sotto forma di anonymous_method_expression è facoltativo. Se forniti, i parametri devono essere tipizzati in modo esplicito. In caso contrario, la funzione anonima può essere convertita in un delegato con qualsiasi elenco di parametri che non contenga parametri di output.

Il corpo del blocco di una funzione anonima è sempre raggiungibile (§13.2).

Esempio: alcuni esempi di funzioni anonime seguono qui sotto:

x => x + 1                             // Implicitly typed, expression body
x => { return x + 1; }                 // Implicitly typed, block body
(int x) => x + 1                       // Explicitly typed, expression body
(int x) => { return x + 1; }           // Explicitly typed, block body
(x, y) => x * y                        // Multiple parameters
() => Console.WriteLine()              // No parameters
async (t1,t2) => await t1 + await t2   // Async
delegate (int x) { return x + 1; }     // Anonymous method expression
delegate { return 1 + 1; }             // Parameter list omitted

esempio finale

Il comportamento di lambda_expressions e anonymous_method_expressions è identico, ad eccezione dei punti seguenti:

  • anonymous_method_expressionconsentono di omettere completamente l'elenco di parametri, producendo convertibilità ai tipi delegati di qualsiasi elenco di parametri di valore.
  • lambda_expressionconsentono di omettere e dedurre i tipi di parametro, mentre anonymous_method_expressionrichiedono che i tipi di parametro vengano dichiarati in modo esplicito.
  • Il corpo di una lambda_expression può essere un'espressione o un blocco, mentre il corpo di una anonymous_method_expression deve essere un blocco.
  • Solo lambda_expressions hanno conversioni in tipi di albero delle espressioni compatibili (§8.6).

12.19.2 Firme di funzioni anonime

La anonymous_function_signature di una funzione anonima definisce i nomi e facoltativamente i tipi dei parametri per la funzione anonima. L'ambito dei parametri della funzione anonima è il anonymous_function_body (§7.7). Insieme all'elenco di parametri (se specificato) il corpo del metodo anonimo costituisce uno spazio di dichiarazione (§7.3). Si verifica quindi un errore in fase di compilazione quando il nome di un parametro della funzione anonima coincide con il nome di una variabile locale, di una costante locale o di un parametro il cui ambito include anonymous_method_expression o lambda_expression.

Se una funzione anonima ha un explicit_anonymous_function_signature, il set di tipi delegati compatibili e tipi di albero delle espressioni è limitato a quelli con gli stessi tipi di parametro e modificatori nello stesso ordine (§10.7). A differenza delle conversioni dei gruppi di metodi (§10.8), la controvarianza dei tipi di parametri delle funzioni anonime non è supportata. Se una funzione anonima non dispone di un anonymous_function_signature, il set di tipi delegati compatibili e tipi di albero delle espressioni è limitato a quelli che non dispongono di parametri di output.

Si noti che un anonymous_function_signature non può includere attributi o una matrice di parametri. Tuttavia, un anonymous_function_signature può essere compatibile con un tipo delegato il cui elenco di parametri contiene una matrice di parametri.

Si noti anche che la conversione in un tipo di albero delle espressioni, anche se compatibile, potrebbe comunque non riuscire in fase di compilazione (§8.6).

12.19.3 Corpi di funzione anonimi

Il corpo (espressione o blocco) di una funzione anonima è soggetto alle seguenti regole:

  • Se la funzione anonima include una firma, i parametri specificati nella firma sono disponibili nel corpo. Se la funzione anonima non ha firma, può essere convertita in un tipo delegato o in un tipo di espressione con parametri (§10.7), ma non è possibile accedere ai parametri nel corpo.
  • Ad eccezione dei parametri by-reference specificati nella sigla (se presenti) della funzione anonima più vicina, è un errore in fase di compilazione se il corpo cerca di accedere a un parametro by-reference.
  • Ad eccezione dei parametri specificati nella firma (se presente) della funzione anonima di inclusione più vicina, si tratta di un errore in fase di compilazione per consentire al corpo di accedere a un parametro di un tipo ref struct.
  • Quando il tipo di this è un tipo di struct, si tratta di un errore in fase di compilazione per consentire al corpo di accedere this. Questo 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 semplicemente tale accesso e non influisce sul fatto che la ricerca dei membri restituisca un membro dello struct.
  • Il corpo ha accesso alle variabili esterne (§12.19.6) della funzione anonima. L'accesso a una variabile esterna si riferirà all'istanza della variabile attiva al momento della valutazione dell' lambda_expression o dell' anonymous_method_expression (§12.19.7).
  • Si tratta di un errore in fase di compilazione perché il corpo contenga un'istruzione goto, un'istruzione break o un'istruzione continue la cui destinazione è esterna al corpo o nel corpo di una funzione anonima contenuta.
  • Un'istruzione return nel corpo restituisce il controllo da una chiamata della funzione anonima di inclusione più vicina, non dal membro della funzione contenitore.

Non è specificato in modo esplicito se esiste un modo per eseguire il blocco di una funzione anonima diverso da attraverso la valutazione e l'invocazione dell'espressione lambda o dell'espressione metodo anonimo . In particolare, un compilatore può scegliere di implementare una funzione anonima sintetizzando uno o più metodi o tipi denominati. I nomi di tali elementi sintetizzati devono essere di un modulo riservato per l'uso del compilatore (§6.4.3).

Risoluzione del sovraccarico 12.19.4

Le funzioni anonime in una lista di argomenti partecipano all'inferenza dei tipi e alla risoluzione delle sovraccarichi. Fare riferimento a §12.6.3 e §12.6.4 per le regole esatte.

esempio: nel seguente esempio viene illustrato l'effetto delle funzioni anonime sulla risoluzione dell'overload.

class ItemList<T> : List<T>
{
    public int Sum(Func<T, int> selector)
    {
        int sum = 0;
        foreach (T item in this)
        {
            sum += selector(item);
        }
        return sum;
    }

    public double Sum(Func<T, double> selector)
    {
        double sum = 0;
        foreach (T item in this)
        {
            sum += selector(item);
        }
        return sum;
    }
}

La classe ItemList<T> dispone di due metodi Sum. Ognuno accetta un argomento selector, che estrae il valore da sommare da un elemento di un elenco. Il valore estratto può essere un int o un double e la somma risultante è analogamente un int o un double.

I metodi Sum possono ad esempio essere utilizzati per calcolare somme da un elenco di righe di dettaglio in un ordine.

class Detail
{
    public int UnitCount;
    public double UnitPrice;
    ...
}

class A
{
    void ComputeSums()
    {
        ItemList<Detail> orderDetails = GetOrderDetails( ... );
        int totalUnits = orderDetails.Sum(d => d.UnitCount);
        double orderTotal = orderDetails.Sum(d => d.UnitPrice * d.UnitCount);
        ...
    }

    ItemList<Detail> GetOrderDetails( ... )
    {
        ...
    }
}

Nella prima chiamata di orderDetails.Sum, entrambi i metodi di Sum sono applicabili perché la funzione anonima d => d.UnitCount è compatibile sia con Func<Detail,int> che con Func<Detail,double>. Tuttavia, la risoluzione dell'overload seleziona il primo metodo Sum perché la conversione in Func<Detail,int> è migliore della conversione in Func<Detail,double>.

Nella seconda chiamata di orderDetails.Sumè applicabile solo il secondo metodo Sum perché la funzione anonima d => d.UnitPrice * d.UnitCount produce un valore di tipo double. Pertanto, la risoluzione del sovraccarico seleziona il secondo metodo Sum per tale chiamata.

esempio finale

12.19.5 Funzioni anonime e associazione dinamica

Una funzione anonima non può essere un ricevitore, un argomento o un operando di un'operazione associata dinamicamente.

12.19.6 Variabili esterne

12.19.6.1 Generale

Qualsiasi variabile locale, parametro di valore o matrice di parametri il cui ambito include l'espressione lambda o l'espressione metodo anonimo è chiamata una variabile esterna della funzione anonima. In un membro di funzione di istanza di una classe, il valore di this è considerato un parametro valore ed è una variabile esterna a qualsiasi funzione anonima contenuta all'interno del membro di funzione.

12.19.6.2 Variabili esterne acquisite

Quando una variabile esterna fa riferimento a una funzione anonima, si dice che la variabile esterna sia stata acquisita dalla funzione anonima. In genere, la durata di una variabile locale è limitata all'esecuzione del blocco o dell'istruzione a cui è associata (§9.2.9.1). Tuttavia, la durata di una variabile esterna catturata viene estesa almeno fino a quando il delegato o un albero delle espressioni creato dalla funzione anonima diventa idoneo per la raccolta dei rifiuti.

Esempio: nell'esempio

delegate int D();

class Test
{
    static D F()
    {
        int x = 0;
        D result = () => ++x;
        return result;
    }

    static void Main()
    {
        D d = F();
        Console.WriteLine(d());
        Console.WriteLine(d());
        Console.WriteLine(d());
    }
}

la variabile locale x viene acquisita dalla funzione anonima e la durata di x viene estesa almeno fino a quando il delegato restituito da F diventa idoneo per l'operazione di Garbage Collection. Poiché ogni chiamata della funzione anonima opera sulla stessa istanza di x, l'output dell'esempio è:

1
2
3

esempio finale

Quando una variabile locale o un parametro di valore viene acquisita da una funzione anonima, la variabile locale o il parametro non viene più considerato come una variabile fissa (§23.4), ma viene invece considerata una variabile spostabile. Tuttavia, le variabili esterne acquisite non possono essere usate in un'istruzione fixed (§23.7), quindi non è possibile acquisire l'indirizzo di una variabile esterna acquisita.

Nota: a differenza di una variabile non incapturata, una variabile locale acquisita può essere esposta simultaneamente a più thread di esecuzione. nota finale

12.19.6.3 Creazione di istanze di variabili locali

Una variabile locale viene considerata istanziata quando l'esecuzione entra nello scopo della variabile.

esempio: Ad esempio, quando viene richiamato il seguente metodo, la variabile locale x viene istanziata e inizializzata tre volte, una per ogni iterazione del ciclo.

static void F()
{
    for (int i = 0; i < 3; i++)
    {
        int x = i * 2 + 1;
        ...
    }
}

Tuttavia, lo spostamento della dichiarazione di x all'esterno del ciclo comporta un'unica istanza di x:

static void F()
{
    int x;
    for (int i = 0; i < 3; i++)
    {
        x = i * 2 + 1;
        ...
    }
}

esempio finale

Quando la variabile non viene acquisita, non è possibile osservare esattamente la frequenza con cui una variabile locale viene istanziata, perché le durate delle istanze sono disgiunte, è possibile che ogni istanza utilizzi semplicemente lo stesso indirizzo di memoria. Tuttavia, quando una funzione anonima acquisisce una variabile locale, gli effetti dell'istanza diventano evidenti.

Esempio: Esempio

delegate void D();
class Test
{
    static D[] F()
    {
        D[] result = new D[3];
        for (int i = 0; i < 3; i++)
        {
            int x = i * 2 + 1;
            result[i] = () => Console.WriteLine(x);
        }
        return result;
    }

    static void Main()
    {
        foreach (D d in F())
        {
            d();
        }
    }
}

produce l'output:

1
3
5

Tuttavia, quando la dichiarazione di x viene spostata all'esterno del ciclo:

delegate void D();

class Test
{
    static D[] F()
    {
        D[] result = new D[3];
        int x;
        for (int i = 0; i < 3; i++)
        {
            x = i * 2 + 1;
            result[i] = () => Console.WriteLine(x);
        }
        return result;
   }

   static void Main()
   {
       foreach (D d in F())
       {
           d();
       }
   }
}

l'output è:

5
5
5

Si noti che un compilatore è autorizzato (ma non obbligato) a ottimizzare le tre instanziazioni in un'unica istanza di un delegato (§10.7.2).

esempio finale

Se un ciclo for dichiara una variabile di iterazione, tale variabile viene considerata dichiarata all'esterno del ciclo.

esempio: pertanto, se l'esempio viene modificato per acquisire la variabile di iterazione stessa:

delegate void D();

class Test
{
    static D[] F()
    {
        D[] result = new D[3];
        for (int i = 0; i < 3; i++)
        {
            result[i] = () => Console.WriteLine(i);
        }
        return result;
   }

   static void Main()
   {
       foreach (D d in F())
       {
           d();
       }
   }
}

viene acquisita una sola istanza della variabile di iterazione, che produce l'output:

3
3
3

esempio finale

È possibile che i delegati di funzione anonimi condividano alcune variabili acquisite, mantenendo però istanze separate di altre.

esempio: ad esempio, se F viene modificato in

static D[] F()
{
    D[] result = new D[3];
    int x = 0;
    for (int i = 0; i < 3; i++)
    {
        int y = 0;
        result[i] = () => Console.WriteLine($"{++x} {++y}");
    }
    return result;
}

i tre delegati acquisiscono la stessa istanza di x ma istanze separate di ye l'output è:

1 1
2 1
3 1

esempio finale

Le funzioni anonime separate possono acquisire la stessa istanza di una variabile esterna.

esempio: nell'esempio:

delegate void Setter(int value);
delegate int Getter();

class Test
{
    static void Main()
    {
        int x = 0;
        Setter s = (int value) => x = value;
        Getter g = () => x;
        s(5);
        Console.WriteLine(g());
        s(10);
        Console.WriteLine(g());
    }
}

le due funzioni anonime acquisiscono la stessa istanza della variabile locale xe possono quindi "comunicare" tramite tale variabile. L'output dell'esempio è:

5
10

esempio finale

12.19.7 Valutazione delle espressioni di funzione anonime

Una funzione anonima F deve sempre essere convertita in un tipo delegato D o in un tipo di albero delle espressioni E, direttamente o tramite l'esecuzione di un'espressione di creazione del delegato new D(F). Questa conversione determina il risultato della funzione anonima, come descritto in §10.7.

Esempio di implementazione 12.19.8

Questa sottochiave è informativa.

Questa sottochiave descrive una possibile implementazione di conversioni di funzioni anonime in termini di altri costrutti C#. L'implementazione descritta di seguito si basa sugli stessi principi usati da un compilatore C# commerciale, ma non è un'implementazione obbligatorio, né è l'unica possibile. Vengono menzionate solo brevemente le conversioni in alberi delle espressioni, perché la semantica esatta non rientra nell'ambito di questa specifica.

Il resto di questa sottochiave fornisce diversi esempi di codice che contiene funzioni anonime con caratteristiche diverse. Per ogni esempio, viene fornita una traduzione corrispondente in codice che utilizza solo altri costrutti del C#. Negli esempi si presuppone che l'identificatore D rappresenti il tipo delegato seguente:

public delegate void D();

La forma più semplice di una funzione anonima è una che non acquisisce variabili esterne:

delegate void D();

class Test
{
    static void F()
    {
        D d = () => Console.WriteLine("test");
    }
}

Questa operazione può essere convertita in un'istanza del delegato che fa riferimento a un metodo statico generato dal compilatore in cui viene inserito il codice della funzione anonima:

delegate void D();

class Test
{
    static void F()
    {
        D d = new D(__Method1);
    }

    static void __Method1()
    {
        Console.WriteLine("test");
    }
}

Nell'esempio seguente la funzione anonima fa riferimento ai membri dell'istanza di this:

delegate void D();

class Test
{
    int x;

    void F()
    {
        D d = () => Console.WriteLine(x);
    }
}

Questa operazione può essere convertita in un metodo di istanza generato dal compilatore contenente il codice della funzione anonima:

delegate void D();

class Test
{
   int x;

   void F()
   {
       D d = new D(__Method1);
   }

   void __Method1()
   {
       Console.WriteLine(x);
   }
}

In questo esempio la funzione anonima acquisisce una variabile locale:

delegate void D();

class Test
{
    void F()
    {
        int y = 123;
        D d = () => Console.WriteLine(y);
    }
}

La durata della variabile locale deve ora essere estesa almeno alla durata del delegato di funzione anonima. A tale scopo, è possibile eseguire l'"hoisting" della variabile locale in un campo di una classe generata dal compilatore. Creazione di istanze della variabile locale (§12.19.6.3) corrisponde quindi alla creazione di un'istanza della classe generata dal compilatore e l'accesso alla variabile locale corrisponde all'accesso a un campo nell'istanza della classe generata dal compilatore. Inoltre, la funzione anonima diventa un metodo di istanza della classe generata dal compilatore:

delegate void D();

class Test
{
    void F()
    {
        __Locals1 __locals1 = new __Locals1();
        __locals1.y = 123;
        D d = new D(__locals1.__Method1);
    }

    class __Locals1
    {
        public int y;

        public void __Method1()
        {
            Console.WriteLine(y);
        }
    }
}

Infine, la funzione anonima seguente acquisisce this e due variabili locali con durate diverse:

delegate void D();

class Test
{
   int x;

   void F()
   {
       int y = 123;
       for (int i = 0; i < 10; i++)
       {
           int z = i * 2;
           D d = () => Console.WriteLine(x + y + z);
       }
   }
}

In questo caso viene creata una classe generata dal compilatore per ogni blocco in cui vengono acquisite variabili locali in modo che le variabili locali nei diversi blocchi possano avere durate indipendenti. Un'istanza di __Locals2, la classe generata dal compilatore per il blocco interno contiene la variabile locale z e un campo che fa riferimento a un'istanza di __Locals1. Un'istanza di __Locals1, la classe generata dal compilatore per il blocco esterno contiene la variabile locale y e un campo che fa riferimento this del membro della funzione contenitore. Con queste strutture di dati, è possibile raggiungere tutte le variabili esterne acquisite tramite un'istanza di __Local2e il codice della funzione anonima può quindi essere implementato come metodo di istanza di tale classe.

delegate void D();

class Test
{
    int x;

    void F()
    {
        __Locals1 __locals1 = new __Locals1();
        __locals1.__this = this;
        __locals1.y = 123;
        for (int i = 0; i < 10; i++)
        {
            __Locals2 __locals2 = new __Locals2();
            __locals2.__locals1 = __locals1;
            __locals2.z = i * 2;
            D d = new D(__locals2.__Method1);
        }
    }

    class __Locals1
    {
        public Test __this;
        public int y;
    }

    class __Locals2
    {
        public __Locals1 __locals1;
        public int z;

        public void __Method1()
        {
            Console.WriteLine(__locals1.__this.x + __locals1.y + z);
        }
    }
}

La stessa tecnica applicata qui per acquisire variabili locali può essere usata anche quando si convertono funzioni anonime in alberi delle espressioni: i riferimenti agli oggetti generati dal compilatore possono essere archiviati nell'albero delle espressioni e l'accesso alle variabili locali può essere rappresentato come accessi ai campi su questi oggetti. Il vantaggio di questo approccio è che consente di condividere le variabili locali "lifted" tra delegati e alberi delle espressioni.

Fine del testo informativo.

12.20 Espressioni di query

12.20.1 Generale

le espressioni di query forniscono una sintassi integrata nel linguaggio per le query simili ai linguaggi di query relazionali e gerarchici, ad esempio SQL e XQuery.

query_expression
    : from_clause query_body
    ;

from_clause
    : 'from' type? identifier 'in' expression
    ;

query_body
    : query_body_clauses? select_or_group_clause query_continuation?
    ;

query_body_clauses
    : query_body_clause
    | query_body_clauses query_body_clause
    ;

query_body_clause
    : from_clause
    | let_clause
    | where_clause
    | join_clause
    | join_into_clause
    | orderby_clause
    ;

let_clause
    : 'let' identifier '=' expression
    ;

where_clause
    : 'where' boolean_expression
    ;

join_clause
    : 'join' type? identifier 'in' expression 'on' expression
      'equals' expression
    ;

join_into_clause
    : 'join' type? identifier 'in' expression 'on' expression
      'equals' expression 'into' identifier
    ;

orderby_clause
    : 'orderby' orderings
    ;

orderings
    : ordering (',' ordering)*
    ;

ordering
    : expression ordering_direction?
    ;

ordering_direction
    : 'ascending'
    | 'descending'
    ;

select_or_group_clause
    : select_clause
    | group_clause
    ;

select_clause
    : 'select' expression
    ;

group_clause
    : 'group' expression 'by' expression
    ;

query_continuation
    : 'into' identifier query_body
    ;

Un'espressione di query inizia con una clausola from e termina con una clausola select o group. La clausola from iniziale può essere seguita da zero o più clausole from, let, where, join o orderby. Ogni clausola from è un generatore che introduce una variabile di intervallo che si estende sugli elementi di una sequenza . Ogni clausola let introduce una variabile di intervallo che rappresenta un valore calcolato tramite variabili di intervallo precedenti. Ogni clausola where è un filtro che esclude gli elementi dal risultato. Ogni clausola join confronta le chiavi specificate della sequenza di origine con le chiavi di un'altra sequenza, producendo coppie corrispondenti. Ogni clausola orderby riordina gli elementi in base ai criteri specificati. La clausola finale select o group specifica la forma del risultato in termini di variabili di intervallo. Infine, una clausola into può essere utilizzata per concatenare le query trattando i risultati di una query come un generatore in una query successiva.

12.20.2 Ambiguità nelle espressioni di query

Le espressioni di query usano una serie di parole chiave contestuali (§6.4.4): ascending, by, descending, equals, from, group, into, join, let, on, orderby, select e where.

Per evitare ambiguità che possono derivare dall'uso di questi identificatori sia come parole chiave che nomi semplici, questi identificatori sono considerati parole chiave in qualsiasi punto all'interno di un'espressione di query, a meno che non siano preceduti da "@" (§6.4.4) nel qual caso sono considerati identificatori. A questo scopo, un'espressione di query è qualsiasi espressione che inizia con "fromidentificatore" seguito da qualsiasi token tranne ";", "=" o ",".

12.20.3 Traduzione delle espressioni di query

12.20.3.1 Generale

Il linguaggio C# non specifica la semantica di esecuzione delle espressioni di query. Le espressioni di query vengono piuttosto convertite in chiamate di metodi che rispettano lo schema di espressione query (§12.20.4). In particolare, le espressioni di query vengono convertite in chiamate di metodi denominati Where, Select, SelectMany, Join, GroupJoin, OrderBy, OrderByDescending, ThenBy, ThenByDescending, GroupBye Cast. Questi metodi devono avere firme particolari e tipi di ritorno, come descritto in §12.20.4. Questi metodi possono essere metodi di istanza dell'oggetto su cui viene eseguita una query o metodi di estensione esterni all'oggetto. Questi metodi implementano l'esecuzione effettiva della query.

La conversione dalle espressioni di interrogazione alle chiamate al metodo è una mappatura sintattica che si verifica prima che venga effettuata qualsiasi associazione di tipi o risoluzione dell'overload. Dopo la conversione delle espressioni di query, le chiamate al metodo risultanti vengono elaborate come chiamate regolari al metodo e ciò può a sua volta rilevare errori in fase di compilazione. Queste condizioni di errore includono, ma non si limitano a, metodi che non esistono, argomenti di tipi sbagliati e metodi generici in cui l'inferenza del tipo non riesce.

Un'espressione di query viene elaborata ripetutamente applicando le traduzioni seguenti fino a quando non sono possibili ulteriori riduzioni. Le traduzioni sono elencate in ordine di applicazione: ogni sezione presuppone che le traduzioni nelle sezioni precedenti siano state eseguite in modo esaustivo e, una volta esaurite, una sezione non verrà successivamente rivisitata nell'elaborazione della stessa espressione di query.

È un errore di compilazione quando un'espressione di query include un'assegnazione a una variabile di intervallo o utilizza una variabile di intervallo come argomento per un parametro di riferimento o di output.

Alcune traduzioni inseriscono variabili di intervallo con identificatori trasparenti indicati da *. Questi sono descritti più avanti in §12.20.3.8.

12.20.3.2 Espressioni di query con continuazioni

Espressione di interrogazione con una continuazione successiva al corpo dell'interrogazione

from «x1» in «e1» «b1» into «x2» «b2»

viene tradotto in

from «x2» in ( from «x1» in «e1» «b1» ) «b2»

Le traduzioni nelle sezioni seguenti presuppongono che le query non abbiano continuazioni.

Esempio: L'esempio:

from c in customers
group c by c.Country into g
select new { Country = g.Key, CustCount = g.Count() }

viene tradotto in:

from g in
   (from c in customers
   group c by c.Country)
select new { Country = g.Key, CustCount = g.Count() }

la traduzione finale della quale è stata:

customers.
GroupBy(c => c.Country).
Select(g => new { Country = g.Key, CustCount = g.Count() })

esempio finale

12.20.3.3 Tipi di variabili di intervallo esplicito

Clausola from che specifica in modo esplicito un tipo di variabile di intervallo

from «T» «x» in «e»

viene tradotto in

from «x» in ( «e» ) . Cast < «T» > ( )

Clausola join che specifica in modo esplicito un tipo di variabile di intervallo

join «T» «x» in «e» on «k1» equals «k2»

viene tradotto in

join «x» in ( «e» ) . Cast < «T» > ( ) on «k1» equals «k2»

Le traduzioni nelle sezioni seguenti presuppongono che le query non abbiano tipi di variabili di intervallo esplicite.

Esempio: Esempio

from Customer c in customers
where c.City == "London"
select c

viene tradotto in

from c in (customers).Cast<Customer>()
where c.City == "London"
select c

che è la traduzione finale di

customers.
Cast<Customer>().
Where(c => c.City == "London")

esempio finale

Nota: i tipi di variabili di intervallo espliciti sono utili per l'esecuzione di query su raccolte che implementano l'interfaccia IEnumerable non generica, ma non l'interfaccia IEnumerable<T> generica. Nell'esempio precedente, questo è il caso se i clienti fossero di tipo ArrayList. nota finale

12.20.3.4 Degenerare espressioni di query

Espressione di query del modulo

from «x» in «e» select «x»

viene tradotto in

( «e» ) . Select ( «x» => «x» )

Esempio: Esempio

from c in customers
select c

viene tradotto in

(customers).Select(c => c)

esempio finale

Un'espressione di query degenerata è una che, banalmente, seleziona gli elementi della sorgente.

Nota: fasi successive della traduzione (§12.20.3.6 e §12.20.3.7) rimuovono le query degenerate introdotte da altri passaggi di traduzione, sostituendole con la loro fonte originale. È tuttavia importante assicurarsi che il risultato di un'espressione di query non sia mai l'oggetto di origine stesso. In caso contrario, la restituzione del risultato di tale query potrebbe esporre inavvertitamente dati privati (ad esempio, una matrice di elementi) a un chiamante. Pertanto, questo passaggio protegge le query degenerate scritte nel codice sorgente direttamente chiamando in modo esplicito Select sul sorgente. Spetta quindi agli implementatori di Select e ad altri operatori di query garantire che questi metodi non restituiscano mai l'oggetto di origine stesso. nota finale

12.20.3.5 Clausole From, let, where, join e orderby

Espressione di query con una seconda clausola from seguita da una clausola select

from «x1» in «e1»  
from «x2» in «e2»  
select «v»

viene tradotto in

( «e1» ) . SelectMany( «x1» => «e2» , ( «x1» , «x2» ) => «v» )

Esempio: Esempio

from c in customers
from o in c.Orders
select new { c.Name, o.OrderID, o.Total }

viene tradotto in

(customers).
SelectMany(c => c.Orders,
(c,o) => new { c.Name, o.OrderID, o.Total }
)

esempio finale

Espressione di query con una seconda clausola from seguita da un corpo della query Q contenente un insieme non vuoto di clausole del corpo della query:

from «x1» in «e1»
from «x2» in «e2»
Q

viene tradotto in

from * in («e1») . SelectMany( «x1» => «e2» ,
                              ( «x1» , «x2» ) => new { «x1» , «x2» } )
Q

Esempio: Esempio

from c in customers
from o in c.Orders
orderby o.Total descending
select new { c.Name, o.OrderID, o.Total }

viene tradotto in

from * in (customers).
   SelectMany(c => c.Orders, (c,o) => new { c, o })
orderby o.Total descending
select new { c.Name, o.OrderID, o.Total }

che è la traduzione finale di

customers.
SelectMany(c => c.Orders, (c,o) => new { c, o }).
OrderByDescending(x => x.o.Total).
Select(x => new { x.c.Name, x.o.OrderID, x.o.Total })

dove x è un identificatore generato dal compilatore che è altrimenti invisibile e inaccessibile.

esempio finale

Un'espressione let insieme alla sua clausola from precedente:

from «x» in «e»  
let «y» = «f»  
...

viene tradotto in

from * in ( «e» ) . Select ( «x» => new { «x» , «y» = «f» } )  
...

Esempio: Esempio

from o in orders
let t = o.Details.Sum(d => d.UnitPrice * d.Quantity)
where t >= 1000
select new { o.OrderID, Total = t }

viene tradotto in

from * in (orders).Select(
    o => new { o, t = o.Details.Sum(d => d.UnitPrice * d.Quantity) })
where t >= 1000
select new { o.OrderID, Total = t }

che è la traduzione finale di

orders
    .Select(o => new { o, t = o.Details.Sum(d => d.UnitPrice * d.Quantity) })
    .Where(x => x.t >= 1000)
    .Select(x => new { x.o.OrderID, Total = x.t })

dove x è un identificatore generato dal compilatore che è altrimenti invisibile e inaccessibile.

esempio finale

Un'espressione where insieme alla sua clausola from precedente:

from «x» in «e»  
where «f»  
...

viene tradotto in

from «x» in ( «e» ) . Where ( «x» => «f» )  
...

Clausola join seguita immediatamente da una clausola select

from «x1» in «e1»  
join «x2» in «e2» on «k1» equals «k2»  
select «v»

viene tradotto in

( «e1» ) . Join( «e2» , «x1» => «k1» , «x2» => «k2» , ( «x1» , «x2» ) => «v» )

Esempio: Esempio

from c in customersh
join o in orders on c.CustomerID equals o.CustomerID
select new { c.Name, o.OrderDate, o.Total }

viene tradotto in

(customers).Join(
   orders,
   c => c.CustomerID, o => o.CustomerID,
   (c, o) => new { c.Name, o.OrderDate, o.Total })

esempio finale

Clausola join seguita da una clausola di corpo della query:

from «x1» in «e1»  
join «x2» in «e2» on «k1» equals «k2»  
...

viene tradotto in

from * in ( «e1» ) . Join(  
«e2» , «x1» => «k1» , «x2» => «k2» ,
( «x1» , «x2» ) => new { «x1» , «x2» })  
...

Clausola join-into seguita immediatamente da una clausola select

from «x1» in «e1»  
join «x2» in «e2» on «k1» equals «k2» into «g»  
select «v»

viene tradotto in

( «e1» ) . GroupJoin( «e2» , «x1» => «k1» , «x2» => «k2» ,
                     ( «x1» , «g» ) => «v» )

Clausola join into seguita da una clausola del corpo della query

from «x1» in «e1»  
join «x2» in «e2» on «k1» equals «k2» into *g»  
...

viene tradotto in

from * in ( «e1» ) . GroupJoin(  
   «e2» , «x1» => «k1» , «x2» => «k2» , ( «x1» , «g» ) => new { «x1» , «g» })
...

Esempio: Esempio

from c in customers
join o in orders on c.CustomerID equals o.CustomerID into co
let n = co.Count()
where n >= 10
select new { c.Name, OrderCount = n }

viene tradotto in

from * in (customers).GroupJoin(
    orders,
    c => c.CustomerID,
    o => o.CustomerID,
    (c, co) => new { c, co })
let n = co.Count()
where n >= 10
select new { c.Name, OrderCount = n }

che è la traduzione finale di

customers
    .GroupJoin(
        orders,
        c => c.CustomerID,
        o => o.CustomerID,
        (c, co) => new { c, co })
    .Select(x => new { x, n = x.co.Count() })
    .Where(y => y.n >= 10)
    .Select(y => new { y.x.c.Name, OrderCount = y.n })

dove x e y sono identificatori generati dal compilatore che sono altrimenti invisibili e inaccessibili.

esempio finale

Clausola orderby e clausola from precedente:

from «x» in «e»  
orderby «k1» , «k2» , ... , «kn»  
...

viene tradotto in

from «x» in ( «e» ) .
OrderBy ( «x» => «k1» ) .
ThenBy ( «x» => «k2» ) .
... .
ThenBy ( «x» => «kn» )
...

Se una clausola ordering specifica un indicatore di direzione decrescente, viene invece generata una chiamata di OrderByDescending o ThenByDescending.

Esempio: Esempio

from o in orders
orderby o.Customer.Name, o.Total descending
select o

ha la traduzione finale

(orders)
    .OrderBy(o => o.Customer.Name)
    .ThenByDescending(o => o.Total)

esempio finale

Le traduzioni seguenti presuppongono che non siano presenti clausole let, where, join o orderby e non più di una clausola iniziale from in ogni espressione di query.

12.20.3.6 Clausole Select

Espressione di query del modulo

from «x» in «e» select «v»

viene tradotto in

( «e» ) . Select ( «x» => «v» )

tranne quando «v» è l'identificatore «x», la traduzione è semplicemente

( «e» )

Esempio: Esempio

from c in customers.Where(c => c.City == "London")
select c

è semplicemente tradotto in

(customers).Where(c => c.City == "London")

esempio finale

Clausole di gruppo 12.20.3.7

Clausola group

from «x» in «e» group «v» by «k»

viene tradotto in

( «e» ) . GroupBy ( «x» => «k» , «x» => «v» )

tranne quando «v» è l'identificatore «x», la traduzione è

( «e» ) . GroupBy ( «x» => «k» )

Esempio: Esempio

from c in customers
group c.Name by c.Country

viene tradotto in

(customers).GroupBy(c => c.Country, c => c.Name)

esempio finale

12.20.3.8 Identificatori trasparenti

Alcune traduzioni inseriscono variabili di intervallo con identificatori trasparenti denotati da *. Gli identificatori trasparenti esistono solo come passaggio intermedio nel processo di conversione delle espressioni di query.

Quando una traduzione di query inserisce un identificatore trasparente, ulteriori passaggi di conversione propagano l'identificatore trasparente in funzioni anonime e inizializzatori di oggetti anonimi. In questi contesti, gli identificatori trasparenti hanno il comportamento seguente:

  • Quando si verifica un identificatore trasparente come parametro in una funzione anonima, i membri del tipo anonimo associato sono automaticamente inclusi nell'ambito nel corpo della funzione anonima.
  • Quando un membro con un identificatore trasparente è nell'ambito, anche i membri di tale membro sono inclusi nell'ambito.
  • Quando un identificatore trasparente si verifica come dichiarazione di membro in un inizializzatore di oggetto anonimo, introduce un membro con un identificatore trasparente.

Nei passaggi di conversione descritti in precedenza, gli identificatori trasparenti vengono sempre introdotti insieme ai tipi anonimi, con lo scopo di acquisire più variabili di intervallo come membri di un singolo oggetto. Un'implementazione di C# può usare un meccanismo diverso rispetto ai tipi anonimi per raggruppare più variabili di intervallo. Gli esempi di traduzione seguenti presuppongono che vengano usati tipi anonimi e mostra una possibile conversione di identificatori trasparenti.

Esempio: Esempio

from c in customers
from o in c.Orders
orderby o.Total descending
select new { c.Name, o.Total }

viene tradotto in

from * in (customers).SelectMany(c => c.Orders, (c,o) => new { c, o })
orderby o.Total descending
select new { c.Name, o.Total }

che viene ulteriormente tradotto in

customers
    .SelectMany(c => c.Orders, (c,o) => new { c, o })
    .OrderByDescending(* => o.Total)
    .Select(\* => new { c.Name, o.Total })

che, quando gli identificatori trasparenti vengono cancellati, equivale a

customers
    .SelectMany(c => c.Orders, (c,o) => new { c, o })
    .OrderByDescending(x => x.o.Total)
    .Select(x => new { x.c.Name, x.o.Total })

dove x è un identificatore generato dal compilatore che è altrimenti invisibile e inaccessibile.

Esempio

from c in customers
join o in orders on c.CustomerID equals o.CustomerID
join d in details on o.OrderID equals d.OrderID
join p in products on d.ProductID equals p.ProductID
select new { c.Name, o.OrderDate, p.ProductName }

viene tradotto in

from * in (customers).Join(
    orders,
    c => c.CustomerID,
    o => o.CustomerID,
    (c, o) => new { c, o })
join d in details on o.OrderID equals d.OrderID
join p in products on d.ProductID equals p.ProductID
select new { c.Name, o.OrderDate, p.ProductName }

che è ulteriormente ridotto a

customers
    .Join(orders, c => c.CustomerID,
        o => o.CustomerID, (c, o) => new { c, o })
    .Join(details, * => o.OrderID, d => d.OrderID, (*, d) => new { *, d })
    .Join(products, * => d.ProductID, p => p.ProductID,
        (*, p) => new { c.Name, o.OrderDate, p.ProductName })

che è la traduzione finale di

customers
    .Join(orders, c => c.CustomerID,
        o => o.CustomerID, (c, o) => new { c, o })
    .Join(details, x => x.o.OrderID, d => d.OrderID, (x, d) => new { x, d })
    .Join(products, y => y.d.ProductID, p => p.ProductID,
        (y, p) => new { y.x.c.Name, y.x.o.OrderDate, p.ProductName })

dove x e y sono identificatori generati dal compilatore che sono altrimenti invisibili e inaccessibili. esempio finale

12.20.4 Modello di espressione di query

Il modello di espressione query stabilisce un modello di metodi che i tipi possono implementare per supportare le espressioni di query.

Un tipo generico C<T> supporta lo schema di espressione di query se i suoi metodi membri pubblici e i metodi di estensione accessibili pubblicamente siano sostituibili dalla seguente definizione di classe. I membri e i metodi di estensione accessibili sono definiti come la "forma" di un tipo generico C<T>. Un tipo generico viene usato per illustrare le relazioni appropriate tra i tipi parametro e restituiti, ma è possibile implementare anche il modello per i tipi non generici.

delegate R Func<T1,R>(T1 arg1);
delegate R Func<T1,T2,R>(T1 arg1, T2 arg2);

class C
{
    public C<T> Cast<T>() { ... }
}

class C<T> : C
{
    public C<T> Where(Func<T,bool> predicate) { ... }
    public C<U> Select<U>(Func<T,U> selector) { ... }
    public C<V> SelectMany<U,V>(Func<T,C<U>> selector,
        Func<T,U,V> resultSelector) { ... }
    public C<V> Join<U,K,V>(C<U> inner, Func<T,K> outerKeySelector,
        Func<U,K> innerKeySelector, Func<T,U,V> resultSelector) { ... }
    public C<V> GroupJoin<U,K,V>(C<U> inner, Func<T,K> outerKeySelector,
        Func<U,K> innerKeySelector, Func<T,C<U>,V> resultSelector) { ... }
    public O<T> OrderBy<K>(Func<T,K> keySelector) { ... }
    public O<T> OrderByDescending<K>(Func<T,K> keySelector) { ... }
    public C<G<K,T>> GroupBy<K>(Func<T,K> keySelector) { ... }
    public C<G<K,E>> GroupBy<K,E>(Func<T,K> keySelector,
        Func<T,E> elementSelector) { ... }
}

class O<T> : C<T>
{
    public O<T> ThenBy<K>(Func<T,K> keySelector) { ... }
    public O<T> ThenByDescending<K>(Func<T,K> keySelector) { ... }
}

class G<K,T> : C<T>
{
    public K Key { get; }
}

I metodi precedenti usano i tipi di delegato generici Func<T1, R> e Func<T1, T2, R>, ma avrebbero potuto ugualmente utilizzare altri tipi di delegato o tipi albero delle espressioni con le stesse relazioni nei tipi di parametro e di restituzione.

Nota: La relazione consigliata tra C<T> e O<T> garantisce che i metodi ThenBy e ThenByDescending siano disponibili solo sul risultato di un OrderBy o di un OrderByDescending. nota finale

Nota: forma consigliata del risultato di GroupBy, sequenza di sequenze, in cui ogni sequenza interna ha una proprietà Key aggiuntiva. nota finale

Nota: poiché le espressioni di query vengono convertite in chiamate di metodo tramite un mapping sintattico, i tipi hanno una notevole flessibilità nel modo in cui implementano tutto o parte del modello di espressione di query. Ad esempio, i metodi del modello possono essere implementati come metodi di istanza o come metodi di estensione perché i due hanno la stessa sintassi di chiamata e i metodi possono richiedere delegati o alberi delle espressioni perché le funzioni anonime sono convertibili in entrambi. I tipi che implementano solo alcuni dei modelli di espressione di query supportano solo le traduzioni di espressioni di query che corrispondono ai metodi supportati da quel tipo. nota finale

Nota: lo spazio dei nomi System.Linq fornisce un'implementazione del modello di espressione di query per qualsiasi tipo che implementa l'interfaccia System.Collections.Generic.IEnumerable<T>. nota finale

12.21 Operatori di assegnazione

12.21.1 Generale

Tutti tranne uno degli operatori di assegnazione assegna un nuovo valore a una variabile, una proprietà, un evento o un elemento indicizzatore. L'eccezione, = ref, assegna un riferimento a una variabile (§9.5) a una variabile di riferimento (§9.7).

assignment
    : unary_expression assignment_operator expression
    ;

assignment_operator
    : '=' 'ref'? | '+=' | '-=' | '*=' | '/=' | '%=' | '&=' | '|=' | '^=' | '<<='
    | right_shift_assignment
    ;

L'operando sinistro di un'assegnazione deve essere un'espressione classificata come variabile oppure, ad eccezione di = ref, un accesso a una proprietà, un accesso a un indicizzatore, un accesso a un evento o una tupla. Un'espressione di dichiarazione non è consentita direttamente come operando sinistro, ma può verificarsi come passaggio nella valutazione di un'assegnazione di decostruzione.

L'operatore = viene chiamato operatore di assegnazione semplice . Assegna il valore o i valori dell'operando destro alla variabile, alla proprietà, all'elemento indicizzatore o agli elementi tuple forniti dall'operando sinistro. L'operando sinistro dell'operatore di assegnazione semplice non è un accesso agli eventi (ad eccezione di quanto descritto in §15.8.2). L'operatore di assegnazione semplice è descritto in §12.21.2.

L'operatore = ref viene chiamato operatore di assegnazione di riferimento . Rende l'operando destro, che deve essere un variable_reference (§9.5), il riferimento della variabile di riferimento designata dall'operando sinistro. L'operatore di assegnazione 'ref' è descritto in §12.21.3.

Gli operatori di assegnazione diversi dagli operatori di = e = ref vengono chiamati operatori di assegnazione composta . Questi operatori eseguono l'operazione indicata sui due operandi e quindi assegnano il valore risultante alla variabile, proprietà o elemento indicizzatore dato dall'operando sinistro. Gli operatori di assegnazione composta sono descritti in §12.21.4.

Gli operatori += e -= con un'espressione di accesso agli eventi come operando sinistro sono detti operatori di assegnazione di eventi . Nessun altro operatore di assegnazione è valido con un accesso a un evento come operando sinistro. Gli operatori di assegnazione di eventi sono descritti in §12.21.5.

Gli operatori di assegnazione sono associativi a destra, il che significa che le operazioni vengono raggruppate da destra verso sinistra.

Esempio: Un'espressione della forma a = b = c viene valutata come a = (b = c). esempio finale

12.21.2 Assegnazione semplice

L'operatore = viene chiamato operatore di assegnazione semplice.

Se l'operando sinistro di un'assegnazione semplice è del formato E.P o E[Ei] in cui E ha il tipo in fase di compilazione dynamic, l'assegnazione viene associata dinamicamente (§12.3.3). In questo caso, il tipo in fase di compilazione dell'espressione di assegnazione è dynamice la risoluzione descritta di seguito verrà eseguita in fase di esecuzione in base al tipo di runtime di E. Se l'operando sinistro è del modulo E[Ei] in cui almeno un elemento di Ei ha il tipo in fase di compilazione dynamice il tipo in fase di compilazione di E non è una matrice, l'accesso dell'indicizzatore risultante è associato dinamicamente, ma con controllo in fase di compilazione limitato (§12.6.5).

Un'assegnazione semplice in cui l'operando sinistro viene classificato come tupla è anche detta un'assegnazione di deconstruttura . Se uno degli elementi della tupla dell'operando sinistro ha un nome di elemento, si verifica un errore in fase di compilazione. Se uno degli elementi della tupla dell'operando sinistro è un declaration_expression e qualsiasi altro elemento non è un declaration_expression o una semplice eliminazione, si verifica un errore in fase di compilazione.

Il tipo di un'assegnazione semplice x = y è il tipo di un'assegnazione a x di y, che viene determinato in modo ricorsivo come indicato di seguito:

  • Se x è un'espressione di tupla (x1, ..., xn)e y può essere decomposta in un'espressione di tupla (y1, ..., yn) con n elementi (§12.7), e ogni assegnazione a xi di yi ha il tipo Ti, allora l'assegnazione ha il tipo (T1, ..., Tn).
  • In caso contrario, se x è classificato come variabile, la variabile non è readonly, x ha un tipo Te y ha una conversione implicita in T, l'assegnazione ha il tipo T.
  • In caso contrario, se x è classificato come variabile tipizzata in modo implicito (ovvero un'espressione di dichiarazione tipizzata in modo implicito) e y ha un tipo T, il tipo dedotto della variabile è Te l'assegnazione ha il tipo T.
  • In caso contrario, se x è classificato come accesso a proprietà o indicizzatore, e la proprietà o l'indicizzatore ha un setter accessibile, x ha un tipo Te y ha una conversione implicita a T, allora l'assegnazione ha il tipo T.
  • Altrimenti, l'assegnazione non è valida e si verifica un errore in fase di binding.

L'elaborazione in fase di esecuzione di un'assegnazione semplice del modulo x = y con tipo T viene eseguita come assegnazione a x di y con tipo T, costituita dai passaggi ricorsivi seguenti:

  • x viene valutato nel caso in cui non lo sia già stato.
  • Se x è classificato come variabile, y viene valutato e, se necessario, convertito in T tramite una conversione implicita (§10,2).
    • Se la variabile specificata da x è un elemento di matrice di un reference_type, viene eseguito un controllo di runtime per assicurarsi che il valore calcolato per y sia compatibile con l'istanza della matrice di cui x è un elemento. Il controllo ha esito positivo se y è nullo se esiste una conversione di riferimento implicita (§10.2.8) dal tipo dell'istanza a cui fa riferimento y al tipo di elemento effettivo dell'istanza di matrice contenente x. In caso contrario, viene generata una System.ArrayTypeMismatchException.
    • Il valore risultante dalla valutazione e dalla conversione di y viene archiviato nella posizione specificata dalla valutazione di xe viene restituito in seguito all'assegnazione.
  • Se x è classificato come accesso a proprietà o indicizzatore:
    • y viene valutato e, se necessario, convertito in T tramite una conversione implicita (§10.2).
    • La funzione di accesso set di x viene richiamata con il valore risultante dalla valutazione e dalla conversione di y come argomento valore.
    • Il valore risultante dalla valutazione e dalla conversione di y viene restituito come risultato dell'assegnazione.
  • Se x è classificato come una tupla (x1, ..., xn) di arità n:
    • y viene decostruito con gli elementi di n in un'espressione a tupla e.
    • Viene creata una tupla di risultati t convertendo e in T usando una conversione di tupla implicita.
    • per ogni xi in ordine da sinistra a destra, viene eseguita un'assegnazione a xi di t.Itemi, eccetto che le xi non vengono valutate di nuovo.
    • t è il risultato ottenuto dall'assegnazione.

Nota: se il tipo di tempo di compilazione di x è dynamic ed è presente una conversione implicita dal tipo di tempo di compilazione di y a dynamic, non è necessaria alcuna risoluzione di runtime. nota finale

Nota: le regole di co-varianza della matrice (§17.6) consentono a un valore di un tipo di matrice A[] di essere un riferimento a un'istanza di un tipo di matrice B[], purché esista una conversione di riferimento implicita da B a A. A causa di queste regole, l'assegnazione a un elemento di matrice di un reference_type richiede un controllo di runtime per assicurarsi che il valore assegnato sia compatibile con l'istanza della matrice. Nell'esempio

string[] sa = new string[10];
object[] oa = sa;
oa[0] = null;              // OK
oa[1] = "Hello";           // OK
oa[2] = new ArrayList();   // ArrayTypeMismatchException

L'ultima assegnazione provoca il lancio di una System.ArrayTypeMismatchException perché un riferimento a un ArrayList non può essere memorizzato in un elemento di un string[].

nota finale

Quando una proprietà o un indicizzatore dichiarato in un struct_type è la destinazione di un'assegnazione, l'espressione di istanza associata alla proprietà o all'accesso dell'indicizzatore deve essere classificata come variabile. Se l'espressione di istanza viene classificata come valore, si verifica un errore di tempo di associazione.

Nota: a causa di §12.8.7, la stessa regola si applica anche ai campi. nota finale

Esempio: dichiarazioni fornite:

struct Point
{
   int x, y;

   public Point(int x, int y)
   {
      this.x = x;
      this.y = y;
   }

   public int X
   {
      get { return x; }
      set { x = value; }
   }

   public int Y {
      get { return y; }
      set { y = value; }
   }
}

struct Rectangle
{
    Point a, b;

    public Rectangle(Point a, Point b)
    {
        this.a = a;
        this.b = b;
    }

    public Point A
    {
        get { return a; }
        set { a = value; }
    }

    public Point B
    {
        get { return b; }
        set { b = value; }
    }
}

nell'esempio

Point p = new Point();
p.X = 100;
p.Y = 100;
Rectangle r = new Rectangle();
r.A = new Point(10, 10);
r.B = p;

Le assegnazioni a p.X, p.Y, r.Ae r.B sono consentite perché p e r sono variabili. Tuttavia, nell'esempio

Rectangle r = new Rectangle();
r.A.X = 10;
r.A.Y = 10;
r.B.X = 100;
r.B.Y = 100;

le assegnazioni non sono valide, poiché r.A e r.B non sono variabili.

esempio finale

12.21.3 Assegnazione di rif.

L'operatore = ref è conosciuto come operatore di assegnazione per riferimento.

L'operando sinistro deve essere un'espressione che si associa a una variabile di riferimento (§9.7), un parametro di riferimento (diverso da this), un parametro di output o un parametro di input. L'operando destro deve essere un'espressione che restituisce un variable_reference che designa un valore dello stesso tipo di quello dell'operando sinistro.

Si tratta di un errore in fase di compilazione se il contesto di riferimento sicuro (§9.7.2) dell'operando sinistro è più ampio del contesto di riferimento sicuro dell'operando destro.

L'operando destro deve essere assegnato sicuramente al momento dell'assegnazione di riferimento.

Quando l'operando sinistro viene associato a un parametro di output, si tratta di un errore se tale parametro di output non è stato assegnato definitivamente all'inizio dell'operatore di assegnazione ref.

Se l'operando sinistro è un riferimento scrivibile (ad esempio, designa qualsiasi elemento diverso da un ref readonly parametro locale o di input), l'operando destro deve essere un variable_referencescrivibile. Se la variabile dell'operando destro è scrivibile, l'operando sinistro può essere un riferimento scrivibile o di sola lettura.

L'operazione rende l'operando sinistro un alias della variabile dell'operando destro. L'alias può essere reso di sola lettura anche se la variabile dell'operando destro è scrivibile.

L'operatore di assegnazione ref restituisce un riferimento_variabile del tipo assegnato. È scrivibile se l'operando sinistro è scrivibile.

L'operatore di assegnazione ref non deve leggere la posizione di archiviazione a cui fa riferimento l'operando destro.

esempio: di seguito sono riportati alcuni esempi dell'uso di = ref:

public static int M1() { ... }
public static ref int M2() { ... }
public static ref uint M2u() { ... }
public static ref readonly int M3() { ... }
public static void Test()
{
int v = 42;
ref int r1 = ref v; // OK, r1 refers to v, which has value 42
r1 = ref M1();      // Error; M1 returns a value, not a reference
r1 = ref M2();      // OK; makes an alias
r1 = ref M2u();     // Error; lhs and rhs have different types
r1 = ref M3();    // error; M3 returns a ref readonly, which r1 cannot honor
ref readonly int r2 = ref v; // OK; make readonly alias to ref
r2 = ref M2();      // OK; makes an alias, adding read-only protection
r2 = ref M3();      // OK; makes an alias and honors the read-only
r2 = ref (r1 = ref M2());  // OK; r1 is an alias to a writable variable,
              // r2 is an alias (with read-only access) to the same variable
}

esempio finale

Nota: durante la lettura del codice utilizzando un operatore = ref, può essere allettante leggere la parte ref come parte dell'operando. Ciò è particolarmente confuso quando l'operando è un'espressione ?: condizionale. Ad esempio, quando si legge ref int a = ref b ? ref x : ref y; è importante interpretarlo considerando = ref come operatore e b ? ref x : ref y come operando destro: ref int a = ref (b ? ref x : ref y);. È importante notare che l'espressione ref b è davvero non parte di tale dichiarazione, anche se potrebbe apparire così a prima vista. nota finale

12.21.4 Assegnazione composta

Se l'operando sinistro di un'assegnazione composta è del formato E.P o E[Ei] in cui E ha il tipo in fase di compilazione dynamic, l'assegnazione viene associata dinamicamente (§12.3.3). In questo caso, il tipo in fase di compilazione dell'espressione di assegnazione è dynamice la risoluzione descritta di seguito verrà eseguita in fase di esecuzione in base al tipo di runtime di E. Se l'operando sinistro è del modulo E[Ei] in cui almeno un elemento di Ei ha il tipo in fase di compilazione dynamice il tipo in fase di compilazione di E non è una matrice, l'accesso dell'indicizzatore risultante è associato dinamicamente, ma con controllo in fase di compilazione limitato (§12.6.5).

Un'operazione del form x «op»= y viene elaborata applicando la risoluzione dell'overload dell'operatore binario (§12.4.5) come se l'operazione fosse scritta x «op» y. Allora

  • Se il tipo restituito dell'operatore selezionato è convertibile in modo implicito nel tipo di x, l'operazione viene valutata come x = x «op» y, ad eccezione del fatto che x viene valutata una sola volta.
  • In caso contrario, se l'operatore selezionato è un operatore predefinito, se il tipo restituito dell'operatore selezionato è convertibile in modo esplicito nel tipo di x e se y è implicitamente convertibile nel tipo di x o l'operatore è un operatore shift, l'operazione viene valutata come x = (T)(x «op» y), dove T è il tipo di x, ad eccezione del fatto che x viene valutato una sola volta.
  • In caso contrario, l'assegnazione composta non è valida e si verifica un errore di tempo di binding.

Il termine "valutato una sola volta" significa che nella valutazione di x «op» yi risultati di qualsiasi espressione costitutiva di x vengono salvati temporaneamente e quindi riutilizzati quando si esegue l'assegnazione a x.

esempio: nell'assegnazione A()[B()] += C(), dove A è un metodo che restituisce int[]e B e C sono metodi che restituiscono int, i metodi vengono richiamati una sola volta, nell'ordine A, B, C. esempio finale

Quando l'operando sinistro di un'assegnazione composta è un accesso alle proprietà o all'indicizzatore, la proprietà o l'indicizzatore avranno sia una funzione di accesso get che una funzione di accesso set. In caso contrario, si verifica un errore di tempo di legame.

La seconda regola precedente consente di valutare x «op»= y come x = (T)(x «op» y) in determinati contesti. La regola esiste in modo che gli operatori predefiniti possano essere usati come operatori composti quando l'operando sinistro è di tipo sbyte, byte, short, ushorto char. Anche quando entrambi gli argomenti sono di uno di questi tipi, gli operatori predefiniti producono un risultato di tipo int, come descritto in §12.4.7.3. Pertanto, senza un cast non sarebbe possibile assegnare il risultato all'operando sinistro.

L'effetto intuitivo della regola per gli operatori predefiniti è semplicemente che x «op»= y è consentito se sono consentiti sia x «op» y che x = y.

Esempio: nel codice seguente

byte b = 0;
char ch = '\0';
int i = 0;
b += 1;           // OK
b += 1000;        // Error, b = 1000 not permitted
b += i;           // Error, b = i not permitted
b += (byte)i;     // OK
ch += 1;          // Error, ch = 1 not permitted
ch += (char)1;    // OK

il motivo intuitivo di ciascun errore è che anche una semplice assegnazione corrispondente sarebbe stata un errore.

esempio finale

Nota: significa anche che le operazioni di assegnazione composta supportano operatori elevati. Poiché un'assegnazione composta x «op»= y è valutata come x = x «op» y o x = (T)(x «op» y), le regole di valutazione coprono implicitamente gli operatori lifted. nota finale

Assegnazione di eventi 12.21.5

Se l'operando sinistro dell'operatore a += or -= viene classificato come accesso a un evento, l'espressione viene valutata come segue:

  • L'espressione di istanza, se presente, dell'accesso all'evento viene valutata.
  • L'operando destro dell'operatore += o -= viene valutato e, se necessario, convertito nel tipo dell'operando sinistro tramite una conversione implicita (§10.2).
  • Viene richiamato un accessor dell'evento, con un elenco di argomenti costituito dal valore calcolato nel passaggio precedente. Se l'operatore è +=, viene richiamato il metodo accessor "add"; se l'operatore è -=, viene richiamato il metodo accessor "remove".

Un'espressione di assegnazione di eventi non restituisce un valore. Pertanto, un'espressione di assegnazione di eventi è valida solo nel contesto di un statement_expression (§13.7).

Espressione 12.22

Un'espressione è un'espressione non_assegnazione o un'assegnazione .

expression
    : non_assignment_expression
    | assignment
    ;

non_assignment_expression
    : declaration_expression
    | conditional_expression
    | lambda_expression
    | query_expression
    ;

12.23 Espressioni costanti

Un'espressione costante è un'espressione che deve essere valutata completamente in fase di compilazione.

constant_expression
    : expression
    ;

Un'espressione costante deve avere il valore null o uno dei tipi seguenti:

  • sbyte, byte, short, ushort, int, uint, long, ulong, char, float, double, decimal, bool, string;
  • un tipo di enumerazione; o
  • un'espressione di valore predefinito (§12.8.21) per un tipo di riferimento.

Nelle espressioni costanti sono consentiti solo i costrutti seguenti:

  • Valori letterali (incluso il valore letterale null).
  • Riferimenti ai membri di tipi di classe e struct const.
  • Riferimenti ai membri dei tipi di enumerazione.
  • Riferimenti alle costanti locali.
  • Sottoespressioni racchiuse tra parentesi, che sono stesse espressioni costanti.
  • Espressioni di casting.
  • checked e unchecked espressioni.
  • nameof espressioni.
  • Gli operatori unari predefiniti +, -, ! (negazione logica) e ~.
  • Operatori binari predefiniti +, -, *, /, %, <<, >>, &, |, ^, &&, ||, ==, !=, <, >, <=e >=.
  • L'operatore condizionale ?:.
  • Operatore null-forgiving ! (§12.8.9).
  • sizeof espressioni, purché il tipo non gestito rientri tra i tipi specificati in §23.6.9 per i quali sizeof restituisce un valore costante.
  • Espressioni di valore predefinite, a condizione che il tipo sia uno dei tipi elencati in precedenza.

Nelle espressioni costanti sono consentite le conversioni seguenti:

  • Conversioni di identità
  • Conversioni numeriche
  • Conversioni di enumerazione
  • Conversioni di espressioni costanti
  • Le conversioni di riferimento implicite ed esplicite, purché l'origine delle conversioni sia un'espressione costante che restituisce il valore null.

Nota: Le altre conversioni, tra cui la conversione di boxing, unboxing e le conversioni implicite di riferimenti di valori nonnull, non sono consentite nelle espressioni costanti. nota finale

Esempio: nel codice seguente

class C
{
    const object i = 5;         // error: boxing conversion not permitted
    const object str = "hello"; // error: implicit reference conversion
}

l'inizializzazione di i è un errore perché è necessaria una conversione boxing. L'inizializzazione di str è un errore perché è necessaria una conversione implicita di riferimento da un valore nonnull.

esempio finale

Ogni volta che un'espressione soddisfa i requisiti elencati in precedenza, l'espressione viene valutata in fase di compilazione. Questo vale anche se l'espressione è una sottoespressione di un'espressione più grande che contiene costrutti non costanti.

La valutazione in fase di compilazione delle espressioni costanti usa le stesse regole della valutazione in fase di esecuzione di espressioni non costanti, ad eccezione del fatto che in cui la valutazione in fase di esecuzione avrebbe generato un'eccezione, la valutazione in fase di compilazione causa un errore in fase di compilazione.

A meno che un'espressione costante non venga inserita in modo esplicito in un contesto di unchecked, gli overflow che si verificano in operazioni aritmetiche di tipo integrale e conversioni durante la valutazione in fase di compilazione dell'espressione causano sempre errori in fase di compilazione (§12.8.20).

Le espressioni costanti sono necessarie nei contesti elencati di seguito ed è indicato nella grammatica usando constant_expression. In questi contesti si verifica un errore in fase di compilazione se un'espressione non può essere valutata completamente in fase di compilazione.

  • Dichiarazioni costanti (§15.4)
  • Dichiarazioni di membri di enumerazione (§19.4)
  • Argomenti predefiniti degli elenchi di parametri (§15.6.2)
  • case etichette di una dichiarazione switch (§13.8.3).
  • istruzioni goto case (§13.10.4)
  • Lunghezze delle dimensioni in un'espressione di creazione di matrice (§12.8.17.5) che include un inizializzatore.
  • Attributi (§22)
  • In un modello_costante (§11.2.3)

Una conversione implicita dell'espressione costante (§10.2.11) consente di convertire un'espressione costante di tipo int in sbyte, byte, short, ushort, uinto ulong, purché il valore dell'espressione costante sia compreso nell'intervallo del tipo di destinazione.

12.24 Espressioni booleane

Un boolean_expression è un'espressione che restituisce un risultato di tipo bool; direttamente o tramite l'applicazione di operator true in determinati contesti, come specificato di seguito.

boolean_expression
    : expression
    ;

L'espressione condizionale di controllo di un if_statement (§13.8.2), while_statement (§13.9.2), do_statement (§13.9.3) o for_statement (§13.9.4) è una boolean_expression. L'espressione condizionale di controllo dell'operatore ?: (§12.18) segue le stesse regole di un boolean_expression, ma per motivi di precedenza dell'operatore viene classificata come null_coalescing_expression.

È necessario un boolean_expressionE per poter produrre un valore di tipo bool, come segue:

  • Se E è convertibile in modo implicito in bool, in fase di esecuzione viene applicata la conversione implicita.
  • In caso contrario, viene utilizzata la risoluzione dell'overload dell'operatore unario (§12.4.4) per trovare un'implementazione unica e migliore di operator true su E, e tale implementazione viene applicata in fase di esecuzione.
  • Se non viene trovato alcun operatore di questo tipo, si verifica un errore di legame al tempo di compilazione.