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 dathis
(§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
+
,-
,*
,/
enew
. 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 esempiox++
). - 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 metodoF
viene chiamato usando il valore precedente dii
, quindi viene chiamato il metodoG
con il valore precedente dii
e, infine, viene chiamato il metodoH
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 comex + (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 comex = (y = z)
. esempio finale
La precedenza e l'associatività possono essere controllate usando parentesi.
esempio:
x + y * z
moltiplica primay
perz
e quindi aggiunge il risultato ax
, ma(x + y) * z
aggiunge primax
ey
e quindi moltiplica il risultato perz
. 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
efalse
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
, as
e 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
oas
. 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 risultatobool
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'operazioneoperator «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 X
e y
è un'espressione di tipo Y
, viene elaborata come segue:
- Si determina il set di operatori candidati definiti dall'utente forniti da
X
eY
per l'operazioneoperator «op»(x, y)
. Il set è costituito dall'unione degli operatori candidati forniti daX
e dagli operatori candidati forniti daY
, ognuno determinato utilizzando le regole di §12.4.6. Per il set combinato, i candidati vengono uniti come segue:- Se
X
eY
sono convertibili tramite identità o seX
eY
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
eY
, un operatore«op»Y
fornito daY
ha lo stesso tipo di restituzione di un«op»X
fornito daX
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
- 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₀
. SeT
è un tipo valore nullable,T₀
è il tipo sottostante; altrimenti,T₀
è uguale aT
. - Per tutte le dichiarazioni
operator «op»
inT₀
e tutte le forme elevate di tali operatori, se almeno un operatore è applicabile (§12.6.4.2) rispetto all'elenco di argomentiA
, l'insieme degli operatori candidati consiste in tutti gli operatori applicabili inT₀
. - 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 diT₀
o dalla classe base effettiva diT₀
seT₀
è 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
, doveb
è unbyte
es
è unshort
, la risoluzione dell'overload selezionaoperator *(int, int)
come miglior operatore. Di conseguenza, l'effetto è cheb
es
vengono convertiti inint
e il tipo del risultato èint
. Analogamente, per l'operazionei * d
, dovei
è unint
ed
è undouble
, la risoluzioneoverload
selezionaoperator *(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
, ushort
o 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 tipodecimal
o si verifica un errore di binding se l'altro operando è di tipofloat
odouble
. - In caso contrario, se uno degli operandi è di tipo
double
, l'altro operando viene convertito nel tipodouble
. - In caso contrario, se uno degli operandi è di tipo
float
, l'altro operando viene convertito nel tipofloat
. - In caso contrario, se uno degli operandi è di tipo
ulong
, l'altro operando viene convertito nel tipoulong
o si verifica un errore di binding se l'altro operando è ditype sbyte
,short
,int
olong
. - In caso contrario, se uno degli operandi è di tipo
long
, l'altro operando viene convertito nel tipolong
. - In caso contrario, se uno degli operandi è di tipo
uint
e l'altro operando è di tiposbyte
,short
oint
, entrambi gli operandi vengono convertiti nel tipolong
. - In caso contrario, se uno degli operandi è di tipo
uint
, l'altro operando viene convertito nel tipouint
. - 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 tipidouble
efloat
. La regola segue dal fatto che non vi sono conversioni implicite tra il tipo didecimal
e i tipidouble
efloat
. 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 diulong
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 undouble
. L'errore viene risolto convertendo in modo esplicito il secondo operando indecimal
, 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 valorenull
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 valorenull
se uno o entrambi gli operandi sononull
(un'eccezione è rappresentata dagli operatori&
e|
del tipobool?
, 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 valorinull
uguali e un valorenull
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 risultatobool
. - 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 valorefalse
se uno o entrambi gli operandi sononull
. In caso contrario, l'operatore alzato svolge gli operandi e applica l'operatore sottostante per ottenere il risultatobool
.
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 denominatiN
in ognuno dei tipi specificati come vincolo primario o vincolo secondario (§15.2.5) perT
, insieme al set di membri accessibili denominatiN
inobject
. - In caso contrario, il set è costituito da tutti i membri accessibili (§7.5) denominati
N
inT
, inclusi i membri ereditati e i membri accessibili denominatiN
inobject
. SeT
è 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 modificatoreoverride
vengono esclusi dal set.
- Se
- Successivamente, se
K
è zero, vengono rimossi tutti i tipi annidati le cui dichiarazioni includono parametri di tipo. SeK
non è zero, vengono rimossi tutti i membri con un numero diverso di parametri di tipo. QuandoK
è 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, doveS
è il tipo in cui il membroM
è 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 diS
vengono rimossi dal set. - Se
M
è una dichiarazione di tipo, tutti i tipi non dichiarati in un tipo di base diS
vengono rimossi dal set e tutte le dichiarazioni di tipo con lo stesso numero di parametri di tipoM
dichiarati in un tipo di base diS
vengono rimosse dal set. - Se
M
è un metodo, tutti i membri non di metodo dichiarati in un tipo di base diS
vengono rimossi dal set.
- Se
- 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 eT
ha sia una classe base efficace diversa daobject
che un set di interfacce non vuoto (§15.2.5). Per ogni membroS.M
nel set, doveS
è il tipo in cui il membroM
viene dichiarato, vengono applicate le regole seguenti seS
è una dichiarazione di classe diversa daobject
:- 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 diM
dichiarata in una dichiarazione di interfaccia vengono rimossi dal set.
- Se
- 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
odynamic
,T
non ha alcun tipo di base. - Se
T
è un enum_type, i tipi di base diT
sono i tipi di classeSystem.Enum
,System.ValueType
eobject
. - Se
T
è un struct_type, i tipi di base diT
sono i tipi di classeSystem.ValueType
eobject
.Nota: un nullable_value_type è un struct_type (§8.3.1). nota finale
- Se
T
è un class_type, i tipi di base diT
sono le classi base diT
, incluso il tipo di classeobject
. - Se
T
è un interface_type, i tipi di base diT
sono le interfacce di base diT
e il tipo di classeobject
. - Se
T
è un array_type, i tipi di base diT
sono i tipi di classeSystem.Array
eobject
. - Se
T
è un delegate_type, i tipi di base diT
sono i tipi di classeSystem.Delegate
eobject
.
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
,y
evalue
indicano espressioni classificate come variabili o valori,T
indica un'espressione classificata come tipo,F
è il nome semplice di un metodo eP
è 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 strutturaT
. 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 die
. Se il metodo èstatic
, si verifica un errore di tempo di associazione. Il metodo viene richiamato con l'espressione di istanzae
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. SeP
è di sola scrittura, si verifica un errore in fase di compilazione. SeP
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)
. SeP
è di sola lettura, si verifica un errore in fase di compilazione. SeP
non èstatic
, l'espressione di istanza èthis
.T.P
Viene richiamato l'accessor get della proprietà P
nella classe o nello structT
. Si verifica un errore in fase di compilazione seP
non èstatic
o seP
è di sola scrittura.T.P = value
La funzione di accesso set della proprietà P
nella classe o nella strutturaT
viene richiamata con l'elenco di argomenti(value)
. Si verifica un errore in fase di compilazione seP
non èstatic
o seP
è di sola lettura.e.P
La funzione di accesso get della proprietà P
nella classe, nella struttura o nell'interfaccia specificata dal tipo diE
viene invocata con l'espressione di istanzae
. Si verifica un errore di binding seP
èstatic
o seP
è 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 diE
viene richiamata con l'espressione di istanzae
e l'elenco di argomenti(value)
. Si verifica un errore di binding seP
èstatic
o seP
è di sola lettura.Accesso a eventi E += value
Viene richiamato l'accessore add dell'evento E
nella classe o struttura contenitore. SeE
non èstatic
, l'espressione di istanza èthis
.E -= value
Viene richiamato l'accessor remove dell'evento E
nella classe o nella struttura contenitore. SeE
non èstatic
, l'espressione di istanza èthis
.T.E += value
Viene richiamata la funzione di accesso add dell'evento E
nella classe o nello structT
. SeE
non èstatic
, si verifica un errore di binding-time.T.E -= value
Viene richiamato l'accessor remove dell'evento E
nella classe o nello structT
. SeE
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 diE
viene richiamata con l'espressione di istanzae
. SeE
è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 diE
viene richiamata con l'espressione di istanzae
. SeE
è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 istanzae
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 istanzae
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
ey
. 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 daM(c: false, valueB);
. Il primo argomento viene usato fuori posizione (l'argomento viene usato in prima posizione, ma il parametro denominatoc
è 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 tipoint
del parametro di input. Nella chiamata al metodoM1(i + 5)
viene creata una variabileint
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 unaSystem.ArrayTypeMismatchException
perché il tipo di elemento effettivo dib
èstring
e nonobject
.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
estring
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 )da a - In caso contrario, se
Eᵢ
ha un tipoU
e il parametro corrispondente è un parametro value (§15.6.2.2), viene un di inferenza con limite inferiore (§12.6.3.10) viene daU
aTᵢ
. - 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'inferenzaesatta (.12.6.3.9 )da a . - In caso contrario, se
Eᵢ
ha un tipoU
e il parametro corrispondente è un parametro di input (§15.6.2.3.2) eEᵢ
è un argomento di input, un 'inferenza esatta (§12.6.3.9) viene daU
aTᵢ
. - In caso contrario, se
ha un tipo e il parametro corrispondente è un parametro di input ( §15.6.2.3.2 ), viene da a . - 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 ) qualsiasisono fisse ( §12.6.3.12 ). - Se non esistono variabili di questo tipo, tutte le variabili di tipo non fissate
Xᵢ
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
- Esiste almeno una variabile di tipo
- 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 ) contengonovariabili di tipo non con prefisso, ma i tipi di input ( §12.6.3.4 ) viene eseguita da a §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 diE
con 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 diE
con tipoT
.
12.6.3.6 Dipendenza
Una variabile di 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 E
a 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 )da a . - 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 da a . - In caso contrario, se
E
è un'espressione con tipoU
, viene fatta un'inferenza con limite inferioredaU
aT
. - 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 E
a un tipo T
nel seguente modo:
- Se
E
è una funzione anonima tipizzata in modo esplicito con tipi di parametroU₁...Uᵥ
eT
è un tipo delegato o un tipo di albero delle espressioni con tipi di parametroV₁...Vᵥ
quindi per ogniUᵢ
un di inferenza esatta (§12.6.3.9) viene daUᵢ
a ilVᵢ
corrispondente.
12.6.3.9 Inferenze esatte
Una di inferenza esatta da un tipo U
a viene eseguita una V
di tipo come indicato di seguito:
- Se
V
è uno deiXᵢ
senza prefisso,U
viene aggiunto all'insieme di limiti esatti perXᵢ
. - In caso contrario, i set di
V₁...Vₑ
eU₁...Uₑ
vengono determinati controllando se si applica uno dei casi seguenti:-
V
è un tipo di matriceV₁[...]
eU
è un tipo di matriceU₁[...]
dello stesso rango -
V
è il tipoV₁?
eU
è il tipoU₁
-
V
è un tipo costruitoC<V₁...Vₑ>
eU
è un tipo costruitoC<U₁...Uₑ>
Se uno di questi casi si applica, viene eseguita una di inferenza esattada 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 U
a viene eseguita una V
di tipo come indicato di seguito:
- Se
V
è uno dei non fissatiXᵢ
,U
viene aggiunto all'insieme di limiti inferiori perXᵢ
. - In caso contrario, se
V
è il tipoV₁?
eU
è il tipoU₁?
, viene eseguita un'inferenza con limite inferiore daU₁
aV₁
. - In caso contrario, i set di
U₁...Uₑ
eV₁...Vₑ
vengono determinati controllando se si applica uno dei casi seguenti:-
V
è un tipo di matriceV₁[...]
eU
è un tipo di matriceU₁[...]
dello stesso rango -
V
è una delleIEnumerable<V₁>
,ICollection<V₁>
,IReadOnlyList<V₁>>
,IReadOnlyCollection<V₁>
oIList<V₁>
eU
è un tipo di matrice unidimensionaleU₁[]
-
V
è unclass
costruito,struct
,interface
odelegate
di tipoC<V₁...Vₑ>
e esiste un tipo univocoC<U₁...Uₑ>
tale cheU
(o, seU
è un tipoparameter
, 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 daU
aC<T>
perchéU₁
potrebbe essereX
oY
.
Se uno di questi casi si applica, viene eseguita un'inferenza da ogniUᵢ
alVᵢ
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 tipoi-th
diC
:- 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 U
V
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ₑ
eU₁...Uₑ
vengono determinati controllando se si applica uno dei casi seguenti:-
U
è un tipo di matriceU₁[...]
eV
è un tipo di matriceV₁[...]
dello stesso rango -
U
è una delleIEnumerable<Uₑ>
,ICollection<Uₑ>
,IReadOnlyList<Uₑ>
,IReadOnlyCollection<Uₑ>
oIList<Uₑ>
eV
è un tipo di matrice unidimensionaleVₑ[]
-
U
è il tipoU1?
eV
è il tipoV1?
-
U
è una classe, una struttura, un'interfaccia o un tipo delegatoC<U₁...Uₑ>
eV
è un tipoclass, struct, interface
odelegate
che èidentical
verso,inherits
da (in modo diretto o indiretto), o implementa (in modo diretto o indiretto) un tipo unicoC<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 daC<U₁>
aV<Q>
. Le inferenze non vengono eseguite daU₁
aX<Q>
oY<Q>
.
Se uno di questi casi si applica, viene eseguita un'inferenza da ogniUᵢ
alVᵢ
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 tipoi-th
diC
:- 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 perXᵢ
. - Ogni limite di
Xᵢ
viene esaminato: per ogni limite esatto U diXᵢ
, tutti i tipiUₑ
che non sono identici aU
vengono rimossi dal set dei candidati. Per ogniU
con limite inferiore diXᵢ
tutti i tipiUₑ
a cui non è una conversione implicita daU
vengono rimossi dal set di candidati. Per ogni U con limite superiore diXᵢ
tutti i tipiUₑ
da cui è presente non una conversione implicita inU
vengono rimossi dal set candidato. - Se tra i tipi candidati rimanenti
Uₑ
esiste un tipo univocoV
a cui esiste una conversione implicita da tutti gli altri tipi candidati, alloraXᵢ
viene fissato aV
. - 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 diF
è il tipo di quell'espressione. - Se il corpo di
F
è un blocco e il set di espressioni nelle istruzionireturn
del blocco ha un miglior tipo comuneT
(§12.6.3.15), il tipo restituito effettivo diF
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 diF
è un'espressione classificata come nulla (§12.2) o un blocco in cui nessuna istruzionereturn
dispone di espressioni, il tipo restituito dedotto è«TaskType»
(§15.15.1). - Se
F
è asincrono e ha un tipo di ritorno dedottoT
, allora il tipo di ritorno dedotto è«TaskType»<T>»
(§15.15.1). - Se
F
non è asincrono e ha un tipo restituito effettivo dedottoT
, 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 classeSystem.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 direttivausing namespace
e data una classeCustomer
con una proprietàName
di tipostring
, è possibile usare il metodoSelect
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
siaCustomer
. Usando quindi il processo di inferenza del tipo di funzione anonima descritto in precedenza,c
viene assegnato il tipoCustomer
e l'espressionec.Name
è correlata al tipo restituito del parametro del selettore, deducendoTResult
dastring
. Pertanto, la chiamata è equivalente aSequence.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'espressioneTimeSpan.Parse(s)
è correlata al tipo di ritorno dif1
, permettendo di dedurre cheY
èSystem.TimeSpan
. Infine, al parametro della seconda funzione anonima,t
, viene assegnato il tipo dedottoSystem.TimeSpan
e l'espressionet.TotalHours
è correlata al tipo restituito dif2
, deducendoZ
dadouble
. Di conseguenza, il risultato della chiamata è di tipodouble
.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 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 aX
. -
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 inferireX
. 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. SeA
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.
- la modalità di passaggio dei parametri dell'argomento è identica alla modalità di passaggio dei parametri del parametro corrispondente e
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.
- Se il gruppo di metodi risulta da un simple_name, un metodo di istanza è applicabile solo se
- 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ᵥ
aQᵥ
non è migliore della conversione implicita daEᵥ
aPᵥ
e - per almeno un argomento, la conversione da
Eᵥ
aPᵥ
è migliore rispetto alla conversione daEᵥ
aQᵥ
.
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 eMₑ
è un metodo generico,Mᵢ
è migliore diMₑ
. - In caso contrario, se
Mᵢ
è applicabile nella forma normale eMₑ
ha una matrice params ed è applicabile solo nel formato espanso,Mᵢ
è migliore diMₑ
. - 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 diMₑ
,Mᵢ
è migliore diMₑ
. - In caso contrario, se
Mᵥ
ha tipi di parametri più specifici diMₓ
,Mᵥ
è migliore diMₓ
. Lasciare che{R1, R2, ..., Rn}
e{S1, S2, ..., Sn}
rappresentino i tipi di parametri non istanziati e non espansi diMᵥ
eMₓ
. i tipi di parametro diMᵥ
sono più specifici diMₓ
se, per ogni parametro,Rx
non è meno specifico diSx
e, per almeno un parametro,Rx
è più specifico diSx
:- 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 inMₓ
,Mᵥ
è migliore diMₓ
. - Se per almeno un parametro
Mᵥ
usa la scelta migliore per il passaggio di parametri (§12.6.4.4) rispetto al parametro corrispondente inMₓ
e nessuno dei parametri inMₓ
usa la scelta migliore di passaggio dei parametri rispetto aMᵥ
, alloraMᵥ
è migliore diMₓ
. - 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 esattamenteT₁
eE
non corrisponde esattamenteT₂
(§12.6.4.6) -
E
corrisponde esattamente sia aT₁
che aT₂
, oppure a nessuno dei due, eT₁
è una destinazione di conversione migliore diT₂
(§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 conversioneC₁
eT₂
non è compatibile con il singolo metodo del gruppo di metodi per la conversioneC₂
12.6.4.6 Corrispondenza esatta dell'espressione
Dato un'espressione E
e un tipo T
, E
corrisponde esattamenteT
se uno dei seguenti casi:
-
E
ha un tipoS
e esiste una conversione di identità daS
aT
-
E
è una funzione anonima,T
è un tipo delegatoD
o un tipo di albero delle espressioniExpression<D>
e vale una delle seguenti condizioni:- Esiste un tipo restituito dedotto
X
perE
nel contesto dell'elenco di parametri diD
(§12.6.3.12) ed esiste una conversione di identità daX
al tipo restituito diD
-
E
è una lambdaasync
senza valore di ritorno eD
ha un tipo di ritorno che è un«TaskType»
non generico -
E
non è asincrono eD
ha un tipo di ritornoY
oppureE
è asincrono eD
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 aY
- Il corpo di
E
è un blocco in cui ogni istruzione return restituisce un'espressione che corrisponde esattamente aY
- Il corpo di
- Esiste un tipo restituito dedotto
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₁
aT₂
e non esiste alcuna conversione implicita daT₂
aT₁
-
T₁
è«TaskType»<S₁>
(§15.15.1),T₂
è«TaskType»<S₂>
eS₁
è una destinazione di conversione migliore diS₂
-
T₁
è«TaskType»<S₁>
(§15.15.1),T₂
è«TaskType»<S₂>
eT₁
è più specializzato diT₂
-
T₁
èS₁
oS₁?
in cuiS₁
è un tipo integrale con segno eT₂
èS₂
oS₂?
doveS₂
è un tipo integrale senza segno. Specificamente:-
S₁
èsbyte
eS₂
èbyte
,ushort
,uint
oulong
-
S₁
èshort
eS₂
èushort
,uint
oulong
-
S₁
èint
eS₂
èuint
oulong
-
S₁
èlong
eS₂
è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
è unV
di tipo valore eM
viene dichiarato o sottoposto a override inV
:-
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 casoE
viene classificato come variabile. - Se
E
non è classificato come variabile o seV
non è un tipo di struct readonly (§16.2.2) eE
è 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
E
e il valore diE
viene assegnato a tale variabile.E
viene quindi riclassificata come riferimento a tale variabile locale temporanea. La variabile temporanea è accessibile comethis
all'interno diM
, ma non in altro modo. Pertanto, solo quando è possibile scrivereE
è possibile che il chiamante osservi le modifiche apportateM
athis
.- L'elenco di argomenti viene valutato come descritto in §12.6.2.
-
M
viene richiamato. La variabile a cui fa riferimentoE
diventa la variabile a cui fa riferimentothis
.
-
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 convertireE
in un class_type, eE
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 diE
è Null, viene generata unaSystem.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 diM
fornito dal tipo di runtime dell'istanza a cui fa riferimentoE
. Questo membro della funzione viene determinato applicando le regole di mapping dell'interfaccia (§18.6.5) per determinare l'implementazione diM
fornita dal tipo di runtime dell'istanza a cui fa riferimentoE
. - In caso contrario, se
M
è un membro di funzione virtuale, il membro della funzione da richiamare è l'implementazione diM
fornita dal tipo di runtime dell'istanza a cui fa riferimentoE
. Questo membro della funzione viene determinato applicando le regole per determinare l'implementazione più derivata (§15.6.4) diM
rispetto al tipo di runtime dell'istanza a cui fa riferimentoE
. - In caso contrario,
M
è un membro di funzione non virtuale e il membro della funzione da richiamare èM
stesso.
- Se il tipo di data e ora di associazione di
- 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
oSystem.Enum
. nota 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 conn
elementi, il risultato della decomposizione è l'espressioneE
stessa. - In caso contrario, se
E
ha un tipo di tupla(T1, ..., Tn)
conn
elementi, alloraE
viene valutato in una variabile temporanea__v
e 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
epointer_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) eSystem.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) eSystem.FormattableString
(§C.3). Le descrizioni dei formati standard, identici per Regular_Interpolation_Format e Verbatim_Interpolation_Format, sono disponibili nella documentazione relativa aSystem.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 numeroI
da0
aN-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 (
}
)
- Carattere parentesi graffa sinistra (
- I caratteri del Interpolated_Regular_String_Mid o Interpolated_Verbatim_String_Mid immediatamente dopo l'interpolazione corrispondente, se presenti
- Specifica segnaposto:
- 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 nomeI
, 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 nomeI
, 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 diT
include un parametro di tipo con nomeI
, il simple_name fa riferimento a tale parametro di tipo. - In caso contrario, se una ricerca di un membro (§12.5) di
I
inT
con argomenti di tipoe
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 athis
. 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 modulothis.I
. Questo problema può verificarsi solo quandoe
è zero. - In caso contrario, il risultato è uguale all'accesso a un membro (§12.8.7) della forma
T.I
oT.I<A₁, ..., Aₑ>
.
- Se
- Se
- 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 eI
è il nome di uno spazio dei nomi inN
, 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 nomeI
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
inN
.
- Se la posizione in cui si verifica il simple_name è racchiusa da una dichiarazione di namespace per
- In caso contrario, se
N
contiene un tipo accessibile avente il nomeI
e parametri di tipoe
, allora:- Se
e
è zero e la posizione in cui si verifica il simple_name è racchiusa da una dichiarazione dello spazio dei nomi perN
e la dichiarazione dello spazio dei nomi contiene un extern_alias_directive o un using_alias_directive che associa il nomeI
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.
- Se
- 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 nomeI
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 tipoe
, 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 tipoe
, il nome semplice risulta ambiguo e si verifica un errore in fase di compilazione.
- Se
Nota: l'intero passaggio è esattamente parallelo al passaggio corrispondente nell'elaborazione di un namespace_or_type_name (§7.8). nota finale
- Se
- In caso contrario, se
e
è zero eI
è 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 essereTi Ni
. - In caso contrario, se
Ei
è nel formatoNi
oE.Ni
oE?.Ni
, l'elemento del tipo di tupla deve essereTi Ni
, a meno che non uno dei blocchi seguenti:- Un altro elemento dell'espressione della tupla ha il nome
Ni
o - Un altro elemento di tupla senza un nome ha un'espressione nella forma di un elemento di tupla
Ni
oE.Ni
oE?.Ni
. -
Ni
è del formatoItemX
, in cuiX
è una sequenza di cifre decimali non avviate da0
che potrebbero rappresentare la posizione di un elemento della tupla eX
non rappresenta la posizione dell'elemento.
- Un altro elemento dell'espressione della tupla ha il nome
- 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
et2
, non usano il tipo dell'espressione di tupla, ma applicano invece una conversione di tupla implicita. Nel caso dit2
, la conversione implicita della tupla si basa sulle conversioni implicite da2
along
e danull
astring
. La terza espressione di tupla ha un tipo(int i, string)
e può quindi essere riclassificata come valore di tale tipo. La dichiarazione dit4
, 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 eE
è uno spazio dei nomi eE
contiene uno spazio dei nomi annidato con nomeI
, il risultato è quello. - Altrimenti, se
E
è uno spazio dei nomi eE
contiene un tipo accessibile con il nomeI
eK
parametri di tipo, allora il risultato è quel tipo costruito con gli argomenti di tipo forniti. - Se
E
è classificato come tipo, seE
non è un parametro di tipo e se una ricerca membro (§12,5) diI
inE
con parametri di tipoK
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
inE
. - In caso contrario, il risultato è una variabile, ovvero il campo statico
I
inE
.
- 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
- 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 seI
fosse un campo statico. - In caso contrario, il risultato è un accesso a eventi senza espressione di istanza associata.
- 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),
- 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
- 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) diI
inT
con argomenti di tipoK
produce una corrispondenza, alloraE.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 diE
. - Se
I
identifica una proprietà dell'istanza, il risultato è un accesso alle proprietà con un'espressione di istanza associata diE
e un tipo associato che corrisponde al tipo della proprietà. SeT
è un tipo di classe, il tipo associato viene selezionato dalla prima dichiarazione o override della proprietà trovata a partire daT
, cercando attraverso le sue classi base. - Se
T
è un class_type eI
identifica un campo dell'istanza di tale class_type:- Se il valore di
E
ènull
, viene generata unaSystem.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 riferimentoE
. - In caso contrario, il risultato è una variabile, ovvero il campo
I
nell'oggetto a cui fa riferimentoE
.
- Se il valore di
- Se
T
è un struct_type eI
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 campoI
nell'istanza dello struct specificata daE
. - In caso contrario, il risultato è una variabile, ovvero il campo
I
nell'istanza dello struct specificato daE
.
- Se
- 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 seI
fosse un campo di istanza. - In caso contrario, il risultato è un accesso agli eventi con un'espressione di istanza associata di
E
.
- 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
- In primo luogo, se
- 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'identificatoreColor
che fanno riferimento al tipoColor
sono delimitate da«...»
, mentre quelle che fanno riferimento al campoColor
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 diP.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 diE
èT?
e il significato diE
è 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
èT
e il significato diE
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'espressioneP.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 diE
èT?
e il significato diE
è 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
èT
e il significato diE
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
restituiscenull
non vengono valutati néA₀
né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
restituiscetrue
,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 tuttaviax
ènull
in fase di esecuzione, verrà generata un'eccezione perché non è possibile eseguire il cast dinull
aint
.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
, sonostring?
, conlv
come parametro di output, e il metodo esegue un'assegnazione semplice.Il metodo
M
passa la variabiles
, di tipostring
, come parametro di output diAssign
. Il compilatore emette un avviso dato ches
non è una variabile nullable. Dato che il secondo argomento diAssign
non 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 conT
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 conT
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 metodiM
:- Se
F
non è generico,F
è un candidato quando:-
M
non dispone di un elenco di argomenti di tipo e -
F
è applicabile per quanto riguardaA
(§12.6.4.2).
-
- Se
F
è generico eM
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 diF
è applicabile per quanto riguardaA
(§12.6.4.2)
- Se
F
è generico eM
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 diF
è applicabile per quanto riguardaA
(§12.6.4.2).
-
- Se
- 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, doveC
è il tipo in cui viene dichiarato il metodoF
, tutti i metodi dichiarati in un tipo di base diC
vengono rimossi dal set. Inoltre, seC
è un tipo di classe diverso daobject
, 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 idoneiMₑ
, 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 idoneiMₑ
, il set di tali metodi di estensione è il set candidato.
- Se l'unità di compilazione o dello spazio dei nomi specificata contiene direttamente dichiarazioni di tipo non generico
- 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
B
metodo ha la precedenza sul primo metodo di estensione eC
metodo 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 suC.G
eE.F
ha la precedenza sia suD.F
che suC.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 diD
ènull
, viene generata unaSystem.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
, long
o 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 tiposhort
viene eseguita una conversione implicita inint
, poiché sono possibili conversioni implicite dashort
aint
e dashort
along
. 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 diP
ènull
, viene generata unaSystem.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 unaSystem.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 T
e 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 inT
o in un tipo base diT
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, doveS
è il tipo in cui viene dichiarato l'indicizzatoreI
:- Se
I
non è applicabile per quanto riguardaA
(§12.6.4.2),I
viene rimosso dal set. - Se
I
è applicabile per quanto riguardaA
(§12.6.4.2), tutti gli indicizzatori dichiarati in un tipo di base diS
vengono rimossi dal set. - Se
I
è applicabile per quanto riguardaA
(§12.6.4.2) eS
è un tipo di classe diverso daobject
, tutti gli indicizzatori dichiarati in un'interfaccia vengono rimossi dal set.
- Se
- 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 diA
e un tipo associato che è il tipo dell'indicizzatore. SeT
è un tipo di classe, il tipo associato viene determinato dalla prima dichiarazione o dall'override dell'indicizzatore trovato cominciando daT
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'espressioneP.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 diE
èT?
e il significato diE
è 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
èT
e il significato diE
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'espressioneP[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 diE
èT?
e il significato diE
è 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
èT
e il significato diE
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
restituiscenull
non vengono valutati néA₀
né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 parametroref
del tipo struct. In particolare, ciò significa che la variabile viene considerata inizialmente assegnata.
- Se la dichiarazione del costruttore non dispone di inizializzatore del costruttore, la variabile
- 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 variabilethis
si comporta esattamente come un parametro di input del tipo struct. - In caso contrario, la variabile
this
si comporta esattamente come un parametroref
del tipo struct
- Se il struct è un
- 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.
- Se il metodo o la funzione di accesso non è un iteratore (§15.14) o una funzione asincrona (§15.15), la variabile
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
, decimal
e 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 dix
. - 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 (sex
è un accesso indicizzatore) associato ax
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 dix
viene richiamato con questo valore come argomento. - Il valore salvato di
x
diventa il risultato dell'operazione.
- L'espressione di istanza (se
Gli operatori ++
e --
supportano anche la notazione del prefisso (§12.9.6). 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.
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 eA
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 perT
come definito in §8.3.3.
- Il object_creation_expression è una chiamata del costruttore predefinita. Il risultato del object_creation_expression è un valore di tipo
- In caso contrario, se
T
è un type_parameter eA
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.
- Se non è stato specificato alcun vincolo di tipo valore o vincolo di costruttore (§15.2.5) per
- 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.
- Se
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 unaSystem.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.
- Viene allocata una nuova istanza della classe
- 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.
- Un'istanza di tipo
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
Rectangle
alloca le due istanze incorporate diPoint
, esse possono essere utilizzate per inizializzare le istanze incorporate diPoint
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 tipoint[,]
e l'espressione di creazione della matrice nuovaint[10][,]
produce un'istanza di matrice di tipoint[][,]
. esempio finale
Ogni espressione nell'elenco di espressioni deve essere di tipo int
, uint
, long
o ulong
o 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'istruzioneint[][] 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é
int
néstring
è 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 daobject[]
. 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) daE
aD
.Se
E
è una funzione anonima, l'espressione di creazione del delegato viene elaborata allo stesso modo di una conversione di funzione anonima (§10,7) daE
aD
.Se
E
è un valore,E
deve essere compatibile (§20.2) conD
, e il risultato è un riferimento a un delegato appena creato con un elenco di chiamate a singola voce che richiamaE
.
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) daE
aD
. - Se
E
è una funzione anonima, la creazione del delegato viene valutata come conversione anonima daE
aD
(§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 unaSystem.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 unaSystem.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 metodoSquare
perché tale metodo corrisponde esattamente all'elenco di parametri e al tipo restituito diDoubleFunc
. Se il secondo metodoSquare
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
ep2
sono dello stesso tipo anonimo.esempio finale
I metodi Equals
e GetHashcode
su tipi anonimi eseguono l'override dei metodi ereditati da object
e 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 metodivoid
, con un'istanza diSystem.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
eSystem.Int32
sono dello stesso tipo. Il risultato ditypeof(X<>)
non dipende dall'argomento di tipo, ma il risultato ditypeof(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
odouble
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 unaSystem.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 unSystem.OverflowException
e il metodoG
restituisce –727379968 (i 32 bit inferiori del risultato out-of-range). Il comportamento del metodoH
dipende dal contesto di controllo overflow predefinito per la compilazione, ma è uguale aF
o uguale aG
.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
eH
causano la segnalazione di errori in fase di compilazione perché le espressioni vengono valutate in un contesto dichecked
. Un overflow si verifica anche quando si valuta l'espressione costante inG
, ma poiché la valutazione avviene in un contesto diunchecked
, 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 dix * y
inMultiply
, quindix * 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 diint
, senza l'operatoreunchecked
, i cast aint
produrrebbero errori in fase di compilazione.esempio finale
Nota: gli operatori e le istruzioni
checked
eunchecked
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 unSpan<int>
, convertito da un operatore implicito inReadOnlySpan<int>
. Analogamente, perspan9
, ilSpan<double>
risultante viene convertito nel tipo definito dall'utenteWidget<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 string
e 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 genericoList<T>
dichiarato all'interno dello spazio dei nomiSystem.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 dinameof(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 diX
è il valore rappresentabile più piccolo del tipo di operando (−2³¹ perint
o −2⁶³ perlong
), allora la negazione matematica diX
non è rappresentabile all'interno del tipo di operando. Se ciò si verifica all'interno di un contestochecked
, viene lanciato unSystem.OverflowException
; se si verifica all'interno di un contestounchecked
, 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 tipolong
e il tipo del risultato èlong
. Un'eccezione è la regola che consente di scrivere il valore diint
−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 valorelong
−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. Sex
è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 tipoSystem.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
, decimal
e 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 dix
. - 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 (sex
è un accesso indicizzatore) associato ax
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 dix
viene richiamata con questo valore come argomento del valore. - Questo valore diventa anche il risultato dell'operazione.
- L'espressione di istanza (se
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 tipox
) o come additive_expression combinata con un parenthesized_expression (che calcola il valorex – 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 dias
eis
.
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
ey
sono identificatori,x.y
è una grammatica corretta per un tipo, anche sex.y
non indica effettivamente un tipo. esempio finale
Nota: dalla regola di disambiguazione, ne consegue che, se
x
ey
sono identificatori,(x)y
,(x)(y)
e(x)(-y)
sono cast_expressions, ma(x)-y
non è, anche sex
identifica un tipo. Tuttavia, sex
è una parola chiave che identifica un tipo predefinito (ad esempioint
), 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 compilazionedynamic
-
t
ha un metodo di istanza o estensione accessibile denominatoGetAwaiter
privo di parametri e di parametri di tipo, e un tipo di ritornoA
per il quale valgono tutte le seguenti condizioni:-
A
implementa l'interfacciaSystem.Runtime.CompilerServices.INotifyCompletion
(in questo caso nota comeINotifyCompletion
per brevità) -
A
dispone di una proprietà dell'istanza accessibile e leggibileIsCompleted
di tipobool
-
A
dispone di un metodo di istanza accessibileGetResult
senza parametri e senza parametri di tipo
-
Lo scopo del metodo 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 èvoid
T
, 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
bool
b
viene ottenuto valutando l'espressione(a).IsCompleted
. - Se
b
èfalse
, la valutazione dipende dal fatto chea
implementi l'interfacciaSystem.Runtime.CompilerServices.ICriticalNotifyCompletion
(in questo caso nota comeICriticalNotifyCompletion
per brevità). Questo controllo viene eseguito in fase di associazione; ovvero, in fase di esecuzione se il tipo in fase di compilazione dia
èdynamic
e in fase di compilazione in caso contrario. Lasciare cher
indichi il delegato per la ripresa (§15.15):- Se
a
non implementaICriticalNotifyCompletion
, viene valutata l'espressione((a) as INotifyCompletion).OnCompleted(r)
. - Se
a
implementaICriticalNotifyCompletion
, 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.
- Se
- Immediatamente dopo (se
b
ètrue
) o al momento della chiamata successiva del delegato di ripresa (seb
è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 è dynamic
e 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 unaSystem.OverflowException
. In un contesto diunchecked
, 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
ey
sono valori finiti positivi.z
è il risultato dix * 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éx
néy
è 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 tipoSystem.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
olong
e l'operando di destra è–1
, si verifica un overflow. In un contesto dichecked
, viene generata unaSystem.ArithmeticException
(o una sottoclasse). In un contesto diunchecked
, è definito dall'implementazione se venga generata unaSystem.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
ey
sono valori finiti positivi.z
è il risultato dix / 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 unaSystem.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 dix
meno la scala diy
.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 dax – (x / y) * y
. Sey
è zero, viene lanciata unaSystem.DivideByZeroException
.Se l'operando sinistro è il valore più piccolo
int
olong
e l'operando destro è–1
, viene generato unSystem.OverflowException
solo se e quandox / 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
ey
sono valori finiti positivi.z
è il risultato dix % y
e viene calcolato comex – n * y
, dove n è il numero intero più grande possibile minore o uguale ax / y
. Questo metodo di calcolo del resto è analogo a quello usato per gli operandi interi, ma differisce dalla definizione IEC 60559 (in cuin
è il numero intero più vicino ax / 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 unaSystem.ArithmeticException
(o una sottoclasse). Un'implementazione conforme non genera un'eccezione perx % y
in qualsiasi caso in cuix / 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 dix
.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 unSystem.OverflowException
. In un contesto diunchecked
, 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
ey
sono valori finiti diversi da zero ez
è il risultato dix + y
. Sex
ey
hanno la stessa grandezza ma segni opposti,z
è zero positivo. Sex + y
è troppo grande per essere rappresentato nel tipo di destinazione,z
è un infinito con lo stesso segno dix + 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 eU
è il tipo sottostante diE
: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 metodoToString
virtuale ereditato dal tipoobject
. SeToString
restituiscenull
, 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 valorenull
. È possibile che venga generata unaSystem.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 è anchenull
). 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 unaSystem.OverflowException
. In un contesto diunchecked
, 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
ey
sono valori finiti diversi da zero ez
è il risultato dix – y
. Sex
ey
sono uguali,z
è zero positivo. Sex – y
è troppo grande per essere rappresentato nel tipo di destinazione,z
è un infinito con lo stesso segno dix – 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 diy
, 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 eU
è il tipo sottostante diE
: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 dix
ey
e 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.
- Se gli elenchi sono uguali, come determinato dall'operatore di uguaglianza di delegato (§12.12.9), il risultato dell'operazione è
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
- Se il primo operando è
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 è dynamic
e 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
<<
spostax
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
>>
spostax
a destra in base a un numero di bit calcolati come descritto di seguito.Quando
x
è di tipoint
olong
, i bit di ordine basso dix
vengono rimossi, i bit rimanenti vengono spostati verso destra e le posizioni di bit vuote dell'ordine elevato vengono impostate su zero sex
è non negativo e impostato su uno sex
è negativo.Quando
x
è di tipouint
oulong
, i bit di ordine basso dix
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
ouint
, il conteggio degli spostamenti viene determinato dai cinque bit meno significativi dicount
. In altre parole, il conteggio dei turni viene calcolato dacount & 0x1F
. - Quando il tipo di
x
èlong
oulong
, il conteggio di spostamento viene determinato dai sei bit meno significativi dicount
. In altre parole, il conteggio dei turni viene calcolato dacount & 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 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 tipoint
, l'operazioneunchecked ((int)((uint)x >> y))
esegue uno spostamento logico a destra dix
. 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 ==
, !=
, <
, >
, <=
, >=
, is
e 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
ey
è 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 U
e «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
null
e l'altro operando è un valore di tipoT
doveT
è 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 ènull
efalse
in caso contrario.
- Se durante l'esecuzione
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
ox != y
, se esiste unoperator ==
ooperator !=
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 tipoobject
.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 seT
potrebbe rappresentare un tipo di valore non nullable e il risultato viene semplicemente definito comefalse
quandoT
è 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
et
fanno riferimento a due istanze di stringa distinte contenenti gli stessi caratteri. Il primo confronto restituisceTrue
perché l'operatore predefinito di uguaglianza di stringhe (§12.12.8) viene selezionato quando entrambi gli operandi sono di tipostring
. Tutti i confronti rimanenti produconoFalse
perché l'overload dioperator ==
nel tipostring
non è applicabile se uno degli operandi ha un tipo di binding-time diobject
.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 valoriint
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 non
null
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 sononull
. - 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.Delegate
e 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 non
null
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.HasValue
e 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 bool
o 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
eyi
in ordine lessicale:- L'operatore
xi == yi
viene valutato e il risultato del tipobool
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'operatorefalse
viene richiamato dinamicamente su di esso e il valorebool
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 valorebool
risultante viene negato con l'operatore di negazione logico (!
).
- Se il confronto ha restituito un
- Se il risultato di
bool
èfalse
, non viene eseguita alcuna ulteriore valutazione e il risultato dell'operatore di uguaglianza della tupla èfalse
.
- L'operatore
- Se i confronti fra tutti gli elementi risultano in
true
, l'operatore di uguaglianza della tupla dà come risultatotrue
.
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
eyi
in ordine lessicale:- L'operatore
xi != yi
viene valutato e il risultato del tipobool
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'operatoretrue
viene richiamato dinamicamente su di esso e il valorebool
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 valorebool
risultante è il risultato.
- Se il confronto ha restituito un
- Se il risultato di
bool
ètrue
, non viene eseguita alcuna ulteriore valutazione e il risultato dell'operatore di uguaglianza della tupla ètrue
.
- L'operatore
- Se i confronti fra tutti gli elementi risultano in
false
, l'operatore di uguaglianza della tupla dà come risultatofalse
.
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:
- Se
E
è una funzione o un gruppo di metodi anonimi, si verifica un errore in fase di compilazione - Se
E
è il valore letteralenull
o se il valore diE
ènull
, il risultato èfalse
. - Altrimenti:
- Sia
R
il tipo di runtime diE
. - È possibile
D
derivare daR
come indicato di seguito: - Se
R
è un tipo di valore nullo,D
è il tipo sottostante diR
. - In caso contrario,
D
èR
. - Il risultato dipende da
D
eT
come indicato di seguito: - Se
T
è un tipo riferimento, il risultato vienetrue
se:- esiste una conversione di identità tra
D
eT
, -
D
è un tipo riferimento e una conversione implicita dei riferimenti daD
aT
esiste oppure - O:
D
è un tipo di valore ed esiste una conversione boxing daD
aT
.
Oppure:D
è un tipo valore eT
è un tipo di interfaccia implementato daD
.
- esiste una conversione di identità tra
- Se
T
è un tipo valore nullable, il risultato ètrue
seD
è il tipo sottostante diT
. - Se
T
è un tipo di valore non a valore nullo, il risultato ètrue
seD
eT
sono dello stesso tipo. - 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, doveC
è il tipo in fase di compilazione diE
:
- Se il tipo in fase di compilazione di
e
è lo stesso diT
, 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 diE
aT
:
- 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
aT
, oppure seC
oT
è 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 tipoT
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 tipoT
.
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
aT
. - Il tipo di
E
oT
è un tipo aperto. -
E
è il valore letteralenull
.
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
diG
è noto come tipo riferimento, perché ha il vincolo di classe. Il parametro di tipoU
diH
non è tuttavia; pertanto l'uso dell'operatoreas
inH
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 U
e «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
è false
o 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
, false
e 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'operazionex & y
, ad eccezione del fatto chey
viene valutata solo sex
non èfalse
. - L'operazione
x || y
corrisponde all'operazionex | y
, ad eccezione del fatto chey
viene valutata solo sex
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
restituiscefalse
eoperator false
restituiscefalse
. In questi casi, né&&
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 bool
o 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 comex ? y : false
. In altre parole,x
viene prima valutato e convertito nel tipobool
. Quindi, sex
ètrue
,y
viene valutato e convertito in tipobool
e questo diventa il risultato dell'operazione. In caso contrario, il risultato dell'operazione èfalse
. - L'operazione
x || y
viene valutata comex ? true : y
. In altre parole,x
viene prima valutato e convertito nel tipobool
. Quindi, sex
ètrue
, il risultato dell'operazione ètrue
. In caso contrario,y
viene valutato e convertito nel tipobool
e 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 tipoT
e restituisce un risultato di tipoT
. -
T
deve contenere dichiarazioni dioperator true
eoperator 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 comeT.false(x) ? x : T.&(x, y)
, doveT.false(x)
è una chiamata deloperator false
dichiarato inT
eT.&(x, y)
è una chiamata deloperator &
selezionato. In altre parole,x
viene valutato per primo eoperator false
viene richiamato sul risultato per determinare sex
è sicuramente falso. Se quindix
è decisamente falso, il risultato dell'operazione è il valore precedentemente calcolato perx
. In caso contrario,y
viene valutato e iloperator &
selezionato viene richiamato sul valore calcolato in precedenza perx
e il valore calcolato pery
per produrre il risultato dell'operazione. - L'operazione
x || y
viene valutata comeT.true(x) ? x : T.|(x, y)
, doveT.true(x)
è una chiamata deloperator true
dichiarato inT
eT.|(x, y)
è una chiamata deloperator |
selezionato. In altre parole,x
viene prima valutato e successivamenteoperator true
viene applicato al risultato per determinare sex
è sicuramente vero. Quindi, sex
è sicuramente true, il risultato dell'operazione è il valore calcolato in precedenza perx
. In caso contrario,y
viene valutato e iloperator |
selezionato viene richiamato sul valore calcolato in precedenza perx
e il valore calcolato pery
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 comea ?? (b ?? c)
. In termini generali, un'espressione del formatoE1 ?? E2 ?? ... ?? EN
restituisce il primo valore degli operandi che non ènull
, oppurenull
nel caso in cui tutti gli operandi sianonull
. 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₀
, A
o 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 eb
è un'espressione dinamica, il tipo di risultato èdynamic
. In fase di esecuzione,a
viene valutata per prima. Sea
non ènull
,a
viene convertito indynamic
e 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 dab
aA₀
, il tipo di risultato èA₀
. In fase di esecuzione,a
viene valutata per prima. Sea
non ènull
,a
viene decomplicato per digitareA₀
e questo diventa il risultato. In caso contrario,b
viene valutato e convertito nel tipoA₀
e questo diventa il risultato. - In caso contrario, se
A
esiste e esiste una conversione implicita dab
aA
, 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 tipoA
e questo diventa il risultato. - Altrimenti, se
A
esiste ed è un tipo di valore nullable,b
ha un tipoB
ed esiste una conversione implicita daA₀
aB
, il tipo di risultato èB
. In fase di esecuzione,a
viene valutata per prima. Sea
non ènull
,a
viene decomposto nel tipoA₀
e convertito nel tipoB
, e questo diventa il risultato. In caso contrario,b
viene valutato e diventa il risultato. - In caso contrario, se
b
ha un tipoB
e esiste una conversione implicita daa
aB
, il tipo di risultato èB
. In fase di esecuzione,a
viene valutata per prima. Sea
non ènull
,a
viene convertito nel tipoB
e 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.Exception
e 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
out
argument_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 dib1
èbool
perché è il tipo del parametro di output corrispondente inM1
. Il successivoWriteLine
è in grado di accedere ai1
eb1
, che sono stati introdotti nell'ambito circostante.La dichiarazione di
s2
mostra un tentativo di utilizzarei2
nella chiamata annidata aM
, il che non è consentito perché il riferimento si trova all'interno dell'elenco degli argomenti in cuii2
è stato dichiarato. D'altra parte, il riferimento ab2
nell'argomento finale è consentito, perché si verifica dopo la fine dell'elenco di argomenti annidati in cui è stato dichiaratob2
.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 avar _
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 comea ? b : (c ? d : e)
. esempio finale
Il primo operando dell'operatore ?:
deve essere un'espressione che può essere convertita in modo implicito in bool
o 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 preferiscedynamic
(§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 tipoX
ey
ha tipoY
,- Se esiste una conversione di identità tra
X
eY
, 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 preferiscedynamic
(§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
aY
, ma non daY
aX
,Y
è il tipo dell'espressione condizionale. - In caso contrario, se esiste una conversione implicita dell'enumerazione (§10.2.4) da
X
aY
,Y
è il tipo dell'espressione condizionale. - In caso contrario, se esiste una conversione implicita dell'enumerazione (§10.2.4) da
Y
aX
,X
è il tipo dell'espressione condizionale. - In caso contrario, se esiste una conversione implicita (§10.2) da
Y
aX
, ma non daX
aY
,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 esiste una conversione di identità tra
- Se solo uno dei
x
ey
ha un tipo e siax
chey
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 valorebool
dib
:- Se esiste una conversione implicita dal tipo di
b
abool
, questa conversione implicita viene eseguita per produrre un valorebool
. - In caso contrario, il
operator true
definito dal tipo dib
viene richiamato per produrre un valorebool
.
- Se esiste una conversione implicita dal tipo di
- 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 valorebool
dib
:- Se esiste una conversione implicita dal tipo di
b
abool
, questa conversione implicita viene eseguita per produrre un valorebool
. - In caso contrario, il
operator true
definito dal tipo dib
viene richiamato per produrre un valorebool
.
- Se esiste una conversione implicita dal tipo di
- 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 diM
èvoid
(§12.8.13). Tuttavia, se viene trattato come null_conditional_invocation_expression, il tipo di risultato può esserevoid
. 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 accederethis
. Questo vale se l'accesso è esplicito (come inthis.x
) o implicito (come inx
dovex
è 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'istruzionebreak
o un'istruzionecontinue
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 metodiSum
. Ognuno accetta un argomentoselector
, che estrae il valore da sommare da un elemento di un elenco. Il valore estratto può essere unint
o undouble
e la somma risultante è analogamente unint
o undouble
.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 diSum
sono applicabili perché la funzione anonimad => d.UnitCount
è compatibile sia conFunc<Detail,int>
che conFunc<Detail,double>
. Tuttavia, la risoluzione dell'overload seleziona il primo metodoSum
perché la conversione inFunc<Detail,int>
è migliore della conversione inFunc<Detail,double>
.Nella seconda chiamata di
orderDetails.Sum
è applicabile solo il secondo metodoSum
perché la funzione anonimad => d.UnitPrice * d.UnitCount
produce un valore di tipodouble
. Pertanto, la risoluzione del sovraccarico seleziona il secondo metodoSum
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 dix
viene estesa almeno fino a quando il delegato restituito daF
diventa idoneo per l'operazione di Garbage Collection. Poiché ogni chiamata della funzione anonima opera sulla stessa istanza dix
, 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 dix
: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 instatic 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 diy
e 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
x
e 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 __Local2
e 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 "from
identificatore" 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
, GroupBy
e 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'interfacciaIEnumerable<T>
generica. Nell'esempio precedente, questo è il caso se i clienti fossero di tipoArrayList
. 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 diSelect
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
ey
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
ey
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>
eO<T>
garantisce che i metodiThenBy
eThenByDescending
siano disponibili solo sul risultato di unOrderBy
o di unOrderByDescending
. 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'interfacciaSystem.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 comea = (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 è dynamic
e 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 dynamic
e 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)
ey
può essere decomposta in un'espressione di tupla(y1, ..., yn)
conn
elementi (§12.7), e ogni assegnazione axi
diyi
ha il tipoTi
, allora l'assegnazione ha il tipo(T1, ..., Tn)
. - In caso contrario, se
x
è classificato come variabile, la variabile non èreadonly
,x
ha un tipoT
ey
ha una conversione implicita inT
, l'assegnazione ha il tipoT
. - In caso contrario, se
x
è classificato come variabile tipizzata in modo implicito (ovvero un'espressione di dichiarazione tipizzata in modo implicito) ey
ha un tipoT
, il tipo dedotto della variabile èT
e l'assegnazione ha il tipoT
. - 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 tipoT
ey
ha una conversione implicita aT
, allora l'assegnazione ha il tipoT
. - 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 inT
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 pery
sia compatibile con l'istanza della matrice di cuix
è un elemento. Il controllo ha esito positivo sey
ènull
o se esiste una conversione di riferimento implicita (§10.2.8) dal tipo dell'istanza a cui fa riferimentoy
al tipo di elemento effettivo dell'istanza di matrice contenentex
. In caso contrario, viene generata unaSystem.ArrayTypeMismatchException
. - Il valore risultante dalla valutazione e dalla conversione di
y
viene archiviato nella posizione specificata dalla valutazione dix
e viene restituito in seguito all'assegnazione.
- Se la variabile specificata da
- Se
x
è classificato come accesso a proprietà o indicizzatore:-
y
viene valutato e, se necessario, convertito inT
tramite una conversione implicita (§10.2). - La funzione di accesso set di
x
viene richiamata con il valore risultante dalla valutazione e dalla conversione diy
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 din
in un'espressione a tuplae
. - Viene creata una tupla di risultati
t
convertendoe
inT
usando una conversione di tupla implicita. - per ogni
xi
in ordine da sinistra a destra, viene eseguita un'assegnazione axi
dit.Itemi
, eccetto che lexi
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 diy
adynamic
, 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 matriceB[]
, purché esista una conversione di riferimento implicita daB
aA
. 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'esempiostring[] 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 unArrayList
non può essere memorizzato in un elemento di unstring[]
.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.A
er.B
sono consentite perchép
er
sono variabili. Tuttavia, nell'esempioRectangle 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
er.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 parteref
come parte dell'operando. Ciò è particolarmente confuso quando l'operando è un'espressione?:
condizionale. Ad esempio, quando si leggeref int a = ref b ? ref x : ref y;
è importante interpretarlo considerando= ref
come operatore eb ? ref x : ref y
come operando destro:ref int a = ref (b ? ref x : ref y);
. È importante notare che l'espressioneref 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 è dynamic
e 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 dynamic
e 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 comex = x «op» y
, ad eccezione del fatto chex
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 sey
è implicitamente convertibile nel tipo dix
o l'operatore è un operatore shift, l'operazione viene valutata comex = (T)(x «op» y)
, doveT
è il tipo dix
, ad eccezione del fatto chex
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» y
i 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()
, doveA
è un metodo che restituisceint[]
eB
eC
sono metodi che restituisconoint
, i metodi vengono richiamati una sola volta, nell'ordineA
,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
, ushort
o 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 comex = x «op» y
ox = (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
eunchecked
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 qualisizeof
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 non
null
, 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 distr
è 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 dichiarazioneswitch
(§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
, uint
o 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
suE
, 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.
ECMA C# draft specification