10 conversioni
10.1 Generale
Una conversione fa sì che un'espressione venga convertita o considerata come di un tipo specifico. Nel caso precedente una conversione può comportare una modifica nella rappresentazione. Le conversioni possono essere implicite o esplicite e determina se è necessario un cast esplicito.
Esempio: ad esempio, la conversione dal tipo
int
al tipolong
è implicita, pertanto le espressioni di tipoint
possono essere considerate in modo implicito come tipolong
. La conversione opposta, dal tipo al tipolong
int
, è esplicita e pertanto è necessario un cast esplicito.int a = 123; long b = a; // implicit conversion from int to long int c = (int) b; // explicit conversion from long to int
esempio finale
Alcune conversioni sono definite dal linguaggio. I programmi possono anche definire le proprie conversioni (§10.5).
Alcune conversioni nel linguaggio vengono definite dalle espressioni ai tipi, altre dai tipi ai tipi. Una conversione da un tipo si applica a tutte le espressioni con tale tipo.
Esempio:
enum Color { Red, Blue, Green } // The expression 0 converts implicitly to enum types Color c0 = 0; // Other int expressions need explicit conversion Color c1 = (Color)1; // Conversion from null expression (no type) to string string x = null; // Conversion from lambda expression to delegate type Func<int, int> square = x => x * x;
esempio finale
10.2 Conversioni implicite
10.2.1 Generale
Le conversioni seguenti vengono classificate come conversioni implicite:
- Conversioni di identità (§10.2.2)
- Conversioni numeriche implicite (§10.2.3)
- Conversioni di enumerazione implicita (§10.2.4)
- Conversioni implicite di stringhe interpolate (§10.2.5)
- Conversioni di riferimenti implicite (§10.2.8)
- Conversioni boxing (§10.2.9)
- Conversioni dinamiche implicite (§10.2.10)
- Conversioni implicite dei parametri di tipo (§10.2.12)
- Conversioni implicite di espressioni costanti (§10.2.11)
- Conversioni implicite definite dall'utente (incluse quelle lifted) (§10.2.14)
- Conversioni di funzioni anonime (§10.2.15)
- Conversioni dei gruppi di metodi (§10.2.15)
- Conversioni di valori letterali Null (§10.2.7)
- Conversioni nullable implicite (§10.2.6)
- Conversioni di tuple implicite (§10.2.13)
- Conversioni letterali predefinite (§10.2.16)
- Conversioni di throw implicite (§10.2.17)
Le conversioni implicite possono verificarsi in diverse situazioni, incluse le chiamate ai membri della funzione (§12.6.6), le espressioni cast (§12.9.7) e le assegnazioni (§12.21).
Le conversioni implicite predefinite hanno sempre esito positivo e non generano mai eccezioni.
Nota: anche le conversioni implicite definite dall'utente progettate correttamente devono presentare queste caratteristiche. nota finale
Ai fini della conversione, i tipi object
e dynamic
sono identity convertibile (§10.2.2).
Tuttavia, le conversioni dinamiche (§10.2.10) si applicano solo alle espressioni di tipo dynamic
(§8.2.4).
10.2.2 Conversione di identità
Una conversione di identità converte da qualsiasi tipo allo stesso tipo o a un tipo equivalente in fase di esecuzione. Un motivo per cui questa conversione esiste è in modo che un tipo T
o un'espressione di tipo T
possa essere convertito in T
se stesso. Esistono le conversioni di identità seguenti:
- Tra
T
eT
, per qualsiasi tipoT
. - Tra
T
eT?
per qualsiasi tipo diT
riferimento . - Tra
object
edynamic
. - Tra tutti i tipi di tupla con la stessa arità e il tipo costruito
ValueTuple<...>
corrispondente, quando esiste una conversione di identità tra ogni coppia di tipi di elemento corrispondenti. - Tra i tipi costruiti dallo stesso tipo generico in cui esiste una conversione identity tra ogni argomento di tipo corrispondente.
Esempio: di seguito viene illustrata la natura ricorsiva della terza regola:
(int a , string b) t1 = (1, "two"); (int c, string d) t2 = (3, "four"); // Identity conversions exist between // the types of t1, t2, and t3. var t3 = (5, "six"); t3 = t2; t2 = t1; var t4 = (t1, 7); var t5 = (t2, 8); // Identity conversions exist between // the types of t4, t5, and t6. var t6 =((8, "eight"), 9); t6 = t5; t5 = t4;
I tipi di tuple e tutti hanno due elementi: un
t1
seguito da un oggettot2
.t3
int
string
I tipi di elemento tupla possono essere usati da tuple, come int4
,t5
et6
. Esiste una conversione di identità tra ogni coppia di tipi di elemento corrispondenti, incluse le tuple annidate, pertanto esiste una conversione di identità tra i tipi di tuplet4
,t5
et6
.esempio finale
Tutte le conversioni di identità sono simmetriche. Se esiste una conversione di identità da T₁
a T₂
, esiste una conversione di identità da T₂
a T₁
. Due tipi sono convertibili in identità quando esiste una conversione di identità tra due tipi.
Nella maggior parte dei casi, una conversione di identità non ha alcun effetto in fase di esecuzione. Tuttavia, poiché le operazioni a virgola mobile possono essere eseguite con precisione superiore rispetto a quanto previsto dal tipo (§8.3.7), l'assegnazione dei risultati può comportare una perdita di precisione e cast espliciti sono garantiti per ridurre la precisione a ciò che è previsto dal tipo (§12.9.7).
10.2.3 Conversioni numeriche implicite
Le conversioni numeriche implicite sono:
- Da
sbyte
ashort
,int
,long
float
,double
, odecimal
. - Da
byte
ashort
,ushort
int
,uint
,long
,ulong
,float
, ,double
odecimal
. - Da
short
aint
,long
float
, ,double
odecimal
. - Da
ushort
aint
,uint
,long
ulong
,float
, ,double
odecimal
. - Da
int
along
,float
double
, odecimal
. - Da
uint
along
,ulong
float
, ,double
odecimal
. - Da
long
afloat
,double
odecimal
. - Da
ulong
afloat
,double
odecimal
. - Da
char
aushort
,int
,uint
long
,ulong
,float
, ,double
odecimal
. - Da
float
adouble
.
Le conversioni da int
, uint
o long
ulong
a float
e da long
o ulong
a double
possono causare una perdita di precisione, ma non causeranno mai una perdita di grandezza. Le altre conversioni numeriche implicite non perdono mai informazioni.
Non esistono conversioni implicite predefinite nel char
tipo, pertanto i valori degli altri tipi integrali non vengono convertiti automaticamente nel char
tipo.
10.2.4 Conversioni di enumerazione implicita
Una conversione di enumerazione implicita consente di convertire un constant_expression (§12.23) con qualsiasi tipo integer e il valore zero da convertire in qualsiasi enum_type e in qualsiasi nullable_value_type il cui tipo sottostante è un enum_type. In quest'ultimo caso la conversione viene valutata convertendo nel enum_type sottostante e eseguendo il wrapping del risultato (§8.3.12).
10.2.5 Conversioni implicite di stringhe interpolate
Una conversione implicita di stringhe interpolate consente la conversione di un interpolated_string_expression (§12.8.3System.FormattableString
System.IFormattable
Quando viene applicata questa conversione, un valore stringa non è composto dalla stringa interpolata. Viene invece creata un'istanza di System.FormattableString
, come descritto più avanti in §12.8.3.
10.2.6 Conversioni implicite nullable
Le conversioni nullable implicite sono quelle conversioni nullable (§10.6.1) derivate da conversioni implicite predefinite.
Conversioni letterali Null 10.2.7
Esiste una conversione implicita dal null
valore letterale a qualsiasi tipo riferimento o tipo di valore nullable. Questa conversione produce un riferimento Null se il tipo di destinazione è un tipo riferimento o il valore Null (§8.3.12) del tipo di valore nullable specificato.
10.2.8 Conversioni di riferimento implicite
Le conversioni di riferimento implicite sono:
- Da qualsiasi reference_type a
object
edynamic
. - Da qualsiasi class_type
S
a qualsiasi class_typeT
, fornitoS
è derivato daT
. - Da qualsiasi
T
, fornitoS
implementa .T
- Da qualsiasi interface_type
S
a qualsiasi interface_typeT
, fornitoS
è derivato daT
. - Da un array_type
S
con un tipo diSᵢ
elemento a un array_typeT
con un tipo diTᵢ
elemento , purché tutte le condizioni seguenti siano vere:-
S
eT
differiscono solo in tipo di elemento. In altre parole,S
eT
hanno lo stesso numero di dimensioni. - Esiste una conversione di riferimento implicita da
Sᵢ
aTᵢ
.
-
- Da un tipo di
S[]
matrice unidimensionale aSystem.Collections.Generic.IList<T>
,System.Collections.Generic.IReadOnlyList<T>
e dalle relative interfacce di base, purché sia presente una conversione implicita di identità o riferimento daS
aT
. - Da qualsiasi array_type a
System.Array
e dalle interfacce implementate. - Da qualsiasi delegate_type a
System.Delegate
e le interfacce implementate. - Dal valore letterale null (§6.4.5.7) a qualsiasi tipo di riferimento.
- Da qualsiasi
T
- Da qualsiasi reference_type a un'interfaccia o a un tipo
T
delegato se ha un'identità implicita o una conversione di riferimento in un tipoT₀
di interfaccia o delegato edT₀
è convertibile a varianza (§18.2.3.3) inT
. - Conversioni implicite che coinvolgono parametri di tipo noti come tipi di riferimento. Per altre informazioni sulle conversioni implicite che coinvolgono parametri di tipo, vedere §10.2.12 .
Le conversioni di riferimento implicite sono quelle conversioni tra reference_typeche possono essere dimostrate sempre riuscite e pertanto non richiedono controlli in fase di esecuzione.
Le conversioni di riferimento, implicite o esplicite, non modificano mai l'identità referenziale dell'oggetto da convertire.
Nota: mentre una conversione di riferimento può modificare il tipo del riferimento, non modifica mai il tipo o il valore dell'oggetto a cui viene fatto riferimento. nota finale
Conversioni boxing 10.2.9
Una conversione boxing consente a un value_type di essere convertito in modo implicito in un reference_type. Esistono le conversioni boxing seguenti:
- Da qualsiasi value_type al tipo
object
. - Da qualsiasi value_type al tipo
System.ValueType
. - Da qualsiasi enum_type al tipo
System.Enum
. - Da qualsiasi non_nullable_value_type a qualsiasi interface_type implementata dal non_nullable_value_type.
- Da qualsiasi non_nullable_value_type a qualsiasi interface_type
I
in modo che sia presente una conversione boxing dal non_nullable_value_type a un altro interface_typeI₀
eI₀
abbia una conversione di identità in .I
- Da qualsiasi
I
in modo che sia presente una conversione boxing dal non_nullable_value_type a un altro interface_typeI₀
eI₀
sia convertibile a varianza (§18.2.3.3) a .I
- Da qualsiasi nullable_value_type a qualsiasi reference_type in cui è presente una conversione boxing dal tipo sottostante del nullable_value_type al reference_type.
- Da un parametro di tipo che non è noto per essere un tipo riferimento a qualsiasi tipo in modo che la conversione sia consentita da §10.2.12.
La conversione boxing di un valore non nullable-value-type consiste nell'allocare un'istanza dell'oggetto e copiare il valore in tale istanza.
La conversione boxing di un valore di un nullable_value_type produce un riferimento Null se è il valore Null (HasValue
è false) oppure il risultato di unwrapping e conversione boxing del valore sottostante in caso contrario.
Nota: il processo di boxing può essere immaginato in termini di esistenza di una classe boxing per ogni tipo di valore. Si consideri, ad esempio, un'implementazione di un'interfaccia
struct S
I
, con una classe boxing denominataS_Boxing
.interface I { void M(); } struct S : I { public void M() { ... } } sealed class S_Boxing : I { S value; public S_Boxing(S value) { this.value = value; } public void M() { value.M(); } }
La conversione boxing di un valore
v
di tipoS
consiste ora nell'esecuzione dell'espressionenew S_Boxing(v)
e nella restituzione dell'istanza risultante come valore del tipo di destinazione della conversione. Di conseguenza, le istruzioniS s = new S(); object box = s;
può essere considerato simile a:
S s = new S(); object box = new S_Boxing(s);
Il tipo di boxing immaginato descritto sopra non esiste effettivamente. Al contrario, un valore boxed di tipo
S
ha il tipoS
di runtime e un controllo del tipo di runtime usando l'operatoreis
con un tipo valore come operando destro verifica se l'operando sinistro è una versione boxed dell'operando destro. ad esempio:int i = 123; object box = i; if (box is int) { Console.Write("Box contains an int"); }
restituirà quanto segue:
Box contains an int
Una conversione boxing implica la creazione di una copia del valore sottoposto a boxing. Questo comportamento è diverso da una conversione di un
object
derivato . Ad esempio, il codice seguentestruct Point { public int x, y; public Point(int x, int y) { this.x = x; this.y = y; } } class A { void M() { Point p = new Point(10, 10); object box = p; p.x = 20; Console.Write(((Point)box).x); } }
restituisce il valore 10 nella console perché l'operazione di boxing implicita che si verifica nell'assegnazione di
p
per farebox
in modo che il valore dip
venga copiato. EraPoint
stato dichiarato invece un oggettoclass
, il valore 20 verrebbe restituito perchép
ebox
farà riferimento alla stessa istanza.L'analogia di una classe boxing non deve essere usata come strumento più che uno strumento utile per illustrare il funzionamento concettuale del boxing. Esistono numerose piccole differenze tra il comportamento descritto da questa specifica e il comportamento che potrebbe derivare dall'implementazione boxing in questo modo.
nota finale
10.2.10 Conversioni dinamiche implicite
Esiste una conversione dinamica implicita da un'espressione di tipo dinamico a qualsiasi tipo T
. La conversione è associata dinamicamente a §12.3.3, il che significa che una conversione implicita verrà ricercata in fase di esecuzione dal tipo di runtime dell'espressione a T
. Se non viene trovata alcuna conversione, viene generata un'eccezione di runtime.
Questa conversione implicita viola apparentemente il consiglio all'inizio di §10.2 che una conversione implicita non dovrebbe mai causare un'eccezione. Tuttavia, non è la conversione stessa, ma la ricerca della conversione che causa l'eccezione. Il rischio di eccezioni in fase di esecuzione è intrinseco nell'uso dell'associazione dinamica. Se l'associazione dinamica della conversione non è desiderata, l'espressione può essere prima convertita in object
e quindi nel tipo desiderato.
Esempio: di seguito vengono illustrate le conversioni dinamiche implicite:
object o = "object"; dynamic d = "dynamic"; string s1 = o; // Fails at compile-time – no conversion exists string s2 = d; // Compiles and succeeds at run-time int i = d; // Compiles but fails at run-time – no conversion exists
Le assegnazioni a
s2
ei
entrambe usano conversioni dinamiche implicite, in cui l'associazione delle operazioni viene sospesa fino al runtime. In fase di esecuzione, vengono cercate conversioni implicite dal tipo di runtime did
(string
) al tipo di destinazione. Viene trovata una conversione instring
ma non inint
.esempio finale
10.2.11 Conversioni implicite di espressioni costanti
Una conversione implicita di espressioni costanti consente le conversioni seguenti:
- Un constant_expression di tipo (§12.23) può essere convertito nel tipo
int
,sbyte
byte
short
ushort
ouint
, purché il valore dellaulong
sia compreso nell'intervallo del tipo di destinazione. - Un constant_expression di tipo
long
può essere convertito in tipoulong
, a condizione che il valore del constant_expression non sia negativo.
10.2.12 Conversioni implicite che coinvolgono parametri di tipo
Per un T
noto come tipo riferimento (§15.2.5), esistono le conversioni di riferimento implicite seguenti (§10.2.8):
- Da
T
alla classeC
base effettiva , daT
a qualsiasi classe di base diC
e daT
a qualsiasi interfaccia implementata daC
. - Da
T
a un interface_typeI
nelT
set di interfacce effettivo e daT
a qualsiasi interfaccia di base diI
. - Da
T
a un parametroU
di tipo specificato cheT
dipende daU
(§15.2.5).Nota: poiché
T
è noto come tipo riferimento, nell'ambito diT
, il tipo di runtime diU
sarà sempre un tipo riferimento, anche seU
non è noto come tipo riferimento in fase di compilazione. nota finale - Dal valore letterale null (§6.4.5.7) a T.
Per un type_parameterT
che non è noto come tipo riferimento §15.2.5, le conversioni seguenti che coinvolgono T
sono considerate conversioni boxing (§10.2.9) in fase di compilazione. In fase di esecuzione, se T
è un tipo di valore, la conversione viene eseguita come conversione boxing. In fase di esecuzione, se T
è un tipo riferimento, la conversione viene eseguita come conversione implicita dei riferimenti o conversione di identità.
- Da
T
alla classeC
base effettiva , daT
a qualsiasi classe di base diC
e daT
a qualsiasi interfaccia implementata daC
.Nota:
C
sarà uno dei tipiSystem.Object
,System.ValueType
oSystem.Enum
(in caso contrarioT
sarebbe noto come tipo riferimento). nota finale - Da
T
a un interface_typeI
nelT
set di interfacce effettivo e daT
a qualsiasi interfaccia di base diI
.
Per un T
che non è noto come tipo riferimento, è presente una conversione implicita da T
a un parametro U
di tipo fornito T
dipende da U
. In fase di esecuzione, se T
è un tipo valore e U
è un tipo riferimento, la conversione viene eseguita come conversione boxing. In fase di esecuzione, se e T
U
sono tipi valore, T
e U
sono necessariamente lo stesso tipo e non viene eseguita alcuna conversione. In fase di esecuzione, se T
è un tipo riferimento, U
è necessariamente anche un tipo riferimento e la conversione viene eseguita come conversione implicita di riferimenti o conversione di identità (§15.2.5).
Per un determinato parametro T
di tipo esistono altre conversioni implicite seguenti:
- Da
T
a un tipo riferimentoS
se ha una conversione implicita in un tipo riferimentoS₀
eS₀
ha una conversione identity inS
. In fase di esecuzione, la conversione viene eseguita allo stesso modo della conversione inS₀
. - Da
T
a un tipo diI
interfaccia se ha una conversione implicita in un tipo diI₀
interfaccia edI₀
è convertibile in varianza inI
(§18.2.3.3). In fase di esecuzione, seT
è un tipo di valore, la conversione viene eseguita come conversione boxing. In caso contrario, la conversione viene eseguita come conversione implicita di riferimento o di identità.
In tutti i casi, le regole assicurano che una conversione venga eseguita come conversione boxing se e solo se in fase di esecuzione la conversione proviene da un tipo valore a un tipo riferimento.
10.2.13 Conversioni di tuple implicite
Esiste una conversione implicita da un'espressione E
di tupla a un tipo T
di tupla se E
ha la stessa arità di T
e esiste una conversione implicita da ogni elemento in E
al tipo di elemento corrispondente in T
. La conversione viene eseguita creando un'istanza del T
tipo corrispondente System.ValueTuple<...>
e inizializzando ognuno dei relativi campi in ordine da sinistra a destra valutando l'espressione dell'elemento tupla corrispondente di E
, convertendola nel tipo di elemento corrispondente di T
utilizzando la conversione implicita trovata e inizializzando il campo con il risultato.
Se un nome di elemento nell'espressione di tupla non corrisponde a un nome di elemento corrispondente nel tipo di tupla, verrà generato un avviso.
Esempio:
(int, string) t1 = (1, "One"); (byte, string) t2 = (2, null); (int, string) t3 = (null, null); // Error: No conversion (int i, string s) t4 = (i: 4, "Four"); (int i, string) t5 = (x: 5, s: "Five"); // Warning: Names are ignored
Le dichiarazioni di ,
t1
t2
et4
sono tutte valide, poiché esistono conversioni implicite dalle espressioni dit5
elemento ai tipi di elemento corrispondenti. La dichiarazione dit3
non è valida perché non è presente alcuna conversione danull
aint
. La dichiarazione dit5
genera un avviso perché i nomi degli elementi nell'espressione di tupla differiscono da quelli nel tipo di tupla.esempio finale
10.2.14 Conversioni implicite definite dall'utente
Una conversione implicita definita dall'utente è costituita da una conversione implicita standard facoltativa, seguita dall'esecuzione di un operatore di conversione implicita definito dall'utente, seguita da un'altra conversione implicita standard facoltativa. Le regole esatte per la valutazione delle conversioni implicite definite dall'utente sono descritte in §10.5.4.
10.2.15 Conversioni di funzioni anonime e conversioni di gruppi di metodi
Le funzioni anonime e i gruppi di metodi non dispongono di tipi in e di se stessi, ma possono essere convertiti in modo implicito in tipi delegati. Inoltre, alcune espressioni lambda possono essere convertite in modo implicito in tipi di albero delle espressioni. Le conversioni di funzioni anonime sono descritte in modo più dettagliato nelle conversioni di gruppi di metodi e di paragrafo 10.8.
10.2.16 Conversioni letterali predefinite
Esiste una conversione implicita da un default_literal (§12.8.21) a qualsiasi tipo. Questa conversione produce il valore predefinito (§9,3) del tipo dedotto.
10.2.17 Conversioni di throw implicite
Anche se le espressioni throw non hanno un tipo, possono essere convertite in modo implicito in qualsiasi tipo.
10.3 Conversioni esplicite
10.3.1 Generale
Le conversioni seguenti vengono classificate come conversioni esplicite:
- Tutte le conversioni implicite (§10.2)
- Conversioni numeriche esplicite (§10.3.2)
- Conversioni esplicite di enumerazione (§10.3.3)
- Conversioni nullable esplicite (§10.3.4)
- Conversioni di tuple esplicite (§10.3.6)
- Conversioni esplicite dei riferimenti (§10.3.5)
- Conversioni esplicite dell'interfaccia
- Conversioni unboxing (§10.3.7)
- Conversioni esplicite dei parametri di tipo (§10.3.8)
- Conversioni esplicite definite dall'utente (§10.3.9)
Le conversioni esplicite possono verificarsi nelle espressioni cast (§12.9.7).
Il set di conversioni esplicite include tutte le conversioni implicite.
Nota: ad esempio, consente l'uso di un cast esplicito quando esiste una conversione implicita di identità, per forzare la selezione di un overload di un metodo specifico. nota finale
Le conversioni esplicite che non sono conversioni implicite sono conversioni che non possono essere dimostrate sempre riuscite, conversioni note probabilmente per perdere informazioni e conversioni tra domini di tipi sufficientemente diversi dal merito di notazione esplicita.
10.3.2 Conversioni numeriche esplicite
Le conversioni numeriche esplicite sono le conversioni da un numeric_type a un'altra numeric_type per cui non esiste già una conversione numerica implicita (§10.2.3):
- Da
sbyte
abyte
,ushort
uint
, ,ulong
ochar
. - Da
byte
asbyte
ochar
. - Da
short
asbyte
,byte
,ushort
uint
,ulong
, ochar
. - Da
ushort
asbyte
,byte
short
, ochar
. - Da
int
asbyte
,byte
,short
ushort
,uint
, ,ulong
ochar
. - Da
uint
asbyte
,byte
,short
ushort
,int
, ochar
. - Da
long
asbyte
,byte
,short
ushort
,int
,uint
, ,ulong
ochar
. - Da
ulong
asbyte
,byte
,short
ushort
,int
,uint
, ,long
ochar
. - Da
char
asbyte
,byte
oshort
. - Da
float
asbyte
,byte
,short
ushort
,int
,uint
,long
, ,ulong
,char
odecimal
. - Da
double
asbyte
,byte
short
,ushort
,int
,uint
,long
,ulong
, ,char
,float
odecimal
. - Da
decimal
asbyte
,byte
short
,ushort
,int
,uint
,long
,ulong
, ,char
,float
odouble
.
Poiché le conversioni esplicite includono tutte le conversioni numeriche implicite ed esplicite, è sempre possibile eseguire la conversione da qualsiasi numeric_type a qualsiasi altra numeric_type usando un'espressione cast (§12.9.7).
Le conversioni numeriche esplicite potrebbero perdere informazioni o causare la generazione di eccezioni. Una conversione numerica esplicita viene elaborata come segue:
- Per una conversione da un tipo integrale a un altro tipo integrale, l'elaborazione dipende dal contesto di controllo dell'overflow (§12.8.20) in cui viene eseguita la conversione:
- In un
checked
contesto la conversione ha esito positivo se il valore dell'operando di origine è compreso nell'intervallo del tipo di destinazione, ma genera un'eccezioneSystem.OverflowException
se il valore dell'operando di origine non è compreso nell'intervallo del tipo di destinazione. - In un
unchecked
contesto, la conversione ha sempre esito positivo e procede come indicato di seguito.- Se il tipo di origine è maggiore del tipo di destinazione, il valore di origine viene troncato rimuovendo i bit più significativi "extra". Il risultato viene quindi trattato come un valore del tipo di destinazione.
- Se il tipo di origine è la stessa dimensione del tipo di destinazione, il valore di origine viene considerato come valore del tipo di destinazione
- In un
- Per una conversione da
decimal
a un tipo integrale, il valore di origine viene arrotondato verso zero al valore integrale più vicino e questo valore integrale diventa il risultato della conversione. Se il valore integrale risultante non è compreso nell'intervallo del tipo di destinazione, viene generata un'eccezioneSystem.OverflowException
. - Per una conversione da
float
odouble
a un tipo integrale, l'elaborazione dipende dal contesto di controllo dell'overflow (§12.8.20) in cui viene eseguita la conversione:- In un contesto controllato, la conversione procede come segue:
- Se il valore dell'operando è NaN o infinito, viene generata un'eccezione
System.OverflowException
. - In caso contrario, l'operando di origine viene arrotondato verso zero al valore integrale più vicino. Se questo valore integrale è compreso nell'intervallo del tipo di destinazione, questo valore è il risultato della conversione.
- In caso contrario viene generata un'eccezione
System.OverflowException
.
- Se il valore dell'operando è NaN o infinito, viene generata un'eccezione
- In un contesto deselezionato, la conversione ha sempre esito positivo e procede come indicato di seguito.
- Se il valore dell'operando è NaN o infinito, il risultato della conversione è un valore non specificato del tipo di destinazione.
- In caso contrario, l'operando di origine viene arrotondato verso zero al valore integrale più vicino. Se questo valore integrale è compreso nell'intervallo del tipo di destinazione, questo valore è il risultato della conversione.
- In caso contrario, il risultato della conversione è un valore non specificato del tipo di destinazione.
- In un contesto controllato, la conversione procede come segue:
- Per una conversione da
double
afloat
, ildouble
valore viene arrotondato al valore piùfloat
vicino. Se ildouble
valore è troppo piccolo per rappresentare comefloat
, il risultato diventa zero con lo stesso segno del valore. Se la grandezza deldouble
valore è troppo grande per rappresentare come ,float
il risultato diventa infinito con lo stesso segno del valore. Se ildouble
valore è NaN, il risultato è anche NaN. - Per una conversione da
float
odouble
indecimal
, il valore di origine viene convertito indecimal
rappresentazione e arrotondato al numero più vicino, se necessario (§8.3.8).- Se il valore di origine è troppo piccolo per rappresentare come
decimal
, il risultato diventa zero, mantenendo il segno del valore originale sedecimal
supporta valori con segno zero. - Se la grandezza del valore di origine è troppo grande per rappresentare come
decimal
, o tale valore è infinito, il risultato è infinito mantenendo il segno del valore originale, se la rappresentazione decimale supporta infiniti; in caso contrario, viene generata un'eccezione System.OverflowException. - Se il valore di origine è NaN, il risultato è NaN se la rappresentazione decimale supporta NaN; in caso contrario, viene generata un'eccezione System.OverflowException.
- Se il valore di origine è troppo piccolo per rappresentare come
- Per una conversione da
decimal
afloat
odouble
, ildecimal
valore viene arrotondato al valore odouble
piùfloat
vicino. Se la grandezza del valore di origine è troppo grande per rappresentare nel tipo di destinazione o tale valore è infinito, il risultato è infinito mantenendo il segno del valore originale. Se il valore di origine è NaN, il risultato è NaN. Anche se questa conversione può perdere precisione, non genera mai un'eccezione.
Nota: il
decimal
tipo non è necessario per supportare valori infiniti o NaN, ma può farlo; il relativo intervallo può essere inferiore all'intervallo difloat
edouble
, ma non è garantito. Perdecimal
le rappresentazioni senza infiniti o valori NaN e con un intervallo minore difloat
, il risultato di una conversione dadecimal
afloat
odouble
non sarà mai infinito o NaN. nota finale
10.3.3 Conversioni esplicite di enumerazione
Le conversioni esplicite di enumerazione sono:
- Da
sbyte
,byte
short
,ushort
,int
uint
long
ulong
char
float
,double
odecimal
a qualsiasi enum_type. - Da qualsiasi enum_type a
sbyte
,byte
short
,ushort
, ,int
,uint
,long
,ulong
char
float
,double
, o .decimal
- Da qualsiasi enum_type a qualsiasi altra enum_type.
Una conversione esplicita dell'enumerazione tra due tipi viene elaborata trattando qualsiasi enum_type partecipante come tipo sottostante di tale enum_type e quindi eseguendo una conversione numerica implicita o esplicita tra i tipi risultanti.
Esempio: dato un enum_type
E
con e il tipo sottostante diint
, una conversione daE
abyte
viene elaborata come conversione numerica esplicita (§10.3.2) da aint
e una conversione dabyte
byte
aE
viene elaborata come conversione numerica implicita (§10.2.3) dabyte
aint
. esempio finale
10.3.4 Conversioni esplicite nullable
Le conversioni nullable esplicite sono quelle conversioni nullable (§10.6.1) derivate da conversioni esplicite e implicite predefinite.
10.3.5 Conversioni esplicite dei riferimenti
Le conversioni di riferimento esplicite sono:
- Dall'oggetto a qualsiasi altro reference_type.
- Da qualsiasi
T
, fornitoS
è una classe base di .T
- Da qualsiasi
T
, fornitoS
non è sealed e nonS
implementa .T
- Da qualsiasi
T
, fornitoT
non è bloccato o viene implementato .T
S
- Da qualsiasi interface_type
S
a qualsiasi interface_typeT
, specificatoS
non è derivato daT
. - Da un array_type
S
con un tipo diSᵢ
elemento a un array_typeT
con un tipo diTᵢ
elemento , purché tutte le condizioni seguenti siano vere:-
S
eT
differiscono solo in tipo di elemento. In altre parole,S
eT
hanno lo stesso numero di dimensioni. - Esiste una conversione di riferimento esplicita da
Sᵢ
aTᵢ
.
-
- Da
System.Array
e le interfacce implementate, a qualsiasi array_type. - Da un array_type
S[]
aSystem.Collections.Generic.IList<T>
,System.Collections.Generic.IReadOnlyList<T>
e dalle relative interfacce di base, purché sia presente una conversione di identità o una conversione esplicita dei riferimenti daS
aT
. - Da
System.Collections.Generic.IList<S>
,System.Collections.Generic.IReadOnlyList<S>
e le relative interfacce di base a un tipo diT[]
matrice unidimensionale , purché sia presente una conversione di identità o una conversione esplicita dei riferimenti daS
a T. - Da
System.Delegate
e le interfacce implementate in qualsiasi delegate_type. - Da un tipo riferimento a un tipo
S
riferimentoT
se ha una conversione di riferimento esplicita daS
a un tipoT₀
riferimento eT₀
viene eseguita una conversione identity daT₀
aT
. - Da un tipo di riferimento a un'interfaccia
S
o a un tipoT
delegato se è presente una conversione di riferimento esplicita daS
a un tipoT₀
di interfaccia o delegato e puòT₀
essere variance convertibile inT
oT
è convertibileT₀
a §18.2.3.3. - Da
D<S₁...Sᵥ>
aD<T₁...Tᵥ>
doveD<X₁...Xᵥ>
è un tipo delegato generico,D<S₁...Sᵥ>
non è compatibile con o identico aD<T₁...Tᵥ>
e per ogni parametroXᵢ
di tipo deiD
blocchi seguenti:- Se
Xᵢ
è invariante,Sᵢ
è identico aTᵢ
. - Se
Xᵢ
è covariante, è presente una conversione di identità, la conversione implicita dei riferimenti o la conversione esplicita dei riferimenti daSᵢ
aTᵢ
. - Se
Xᵢ
è controvariante,Sᵢ
eTᵢ
sono identici o entrambi i tipi di riferimento.
- Se
- Conversioni esplicite che coinvolgono parametri di tipo noti come tipi di riferimento. Per altre informazioni sulle conversioni esplicite che coinvolgono parametri di tipo, vedere §10.3.8.
Le conversioni di riferimento esplicite sono quelle conversioni tra reference_typeche richiedono controlli di runtime per assicurarsi che siano corretti.
Affinché una conversione di riferimento esplicita abbia esito positivo in fase di esecuzione, il valore dell'operando di origine deve essere null
o il tipo dell'oggetto a cui fa riferimento l'operando di origine deve essere un tipo che può essere convertito nel tipo di destinazione da una conversione di riferimento implicita (§10.2.8). Se una conversione di riferimento esplicito ha esito negativo, viene generata un'eccezione System.InvalidCastException
.
Nota: le conversioni di riferimento, implicite o esplicite, non modificano mai il valore del riferimento stesso (§8.2.1), solo il tipo; né modifica il tipo o il valore dell'oggetto a cui viene fatto riferimento. nota finale
10.3.6 Conversioni di tuple esplicite
Esiste una conversione esplicita da un'espressione E
di tupla a un tipo T
di tupla se E
ha la stessa arità di T
e esiste una conversione implicita o esplicita da ogni elemento in E
al tipo di elemento corrispondente in .T
La conversione viene eseguita creando un'istanza del T
tipo corrispondente System.ValueTuple<...>
e inizializzando ognuno dei relativi campi in ordine da sinistra a destra valutando l'espressione dell'elemento tupla corrispondente di E
, convertendola nel tipo di elemento corrispondente di T
utilizzando la conversione esplicita trovata e inizializzando il campo con il risultato.
10.3.7 Conversioni unboxing
Una conversione unboxing consente di convertire in modo esplicito un reference_type in un value_type. Esistono le conversioni unboxing seguenti:
- Dal tipo
object
a qualsiasi value_type. - Dal tipo
System.ValueType
a qualsiasi value_type. - Dal tipo
System.Enum
a qualsiasi enum_type. - Da qualsiasi interface_type a qualsiasi non_nullable_value_type che implementa l'interface_type.
- Da qualsiasi interface_type a qualsiasi
I
in cui è presente una conversione unboxing da un interface_typeI₀
al tipo di non_nullable_value e una conversione di identità daI
a .I₀
- Da qualsiasi interface_type a qualsiasi non_nullable_value_type
I
in cui è presente una conversione unboxing da un interface_typeI₀
all'non_nullable_value_type edI₀
è variance_convertible aI
oI
è convertibileI₀
in varianza in (§18.2.3.3). - Da qualsiasi reference_type a qualsiasi nullable_value_type in cui è presente una conversione unboxing da reference_type al non_nullable_value_type sottostante del nullable_value_type.
- Da un parametro di tipo che non è noto per essere un tipo valore a qualsiasi tipo in modo che la conversione sia consentita da §10.3.8.
Un'operazione unboxing in un non_nullable_value_type consiste innanzitutto nel verificare che l'istanza dell'oggetto sia un valore boxed del non_nullable_value_type specificato e quindi copiare il valore dall'istanza.
Unboxing in un nullable_value_type produce il valore Null del nullable_value_type se l'operando di origine è null
o il risultato di unboxing dell'istanza dell'oggetto al tipo sottostante del nullable_value_type in caso contrario.
Nota: facendo riferimento alla classe boxing immaginaria descritta in §10.2.9, una conversione unboxing di una casella oggetto in un value_type
S
consiste nell'eseguire l'espressione((S_Boxing)box).value
. Di conseguenza, le istruzioniobject box = new S(); S s = (S)box;
concettualmente corrispondono a
object box = new S_Boxing(new S()); S s = ((S_Boxing)box).value;
nota finale
Affinché una conversione unboxing in un determinato non_nullable_value_type abbia esito positivo in fase di esecuzione, il valore dell'operando di origine deve essere un riferimento a un valore boxed di tale non_nullable_value_type. Se viene generato l'operando null
System.NullReferenceException
di origine. Se l'operando di origine è un riferimento a un oggetto incompatibile, viene generata un'eccezione System.InvalidCastException
.
Affinché una conversione unboxing in un determinato nullable_value_type abbia esito positivo in fase di esecuzione, il valore dell'operando di origine deve essere Null o un riferimento a un valore boxed del non_nullable_value_type sottostante del nullable_value_type. Se l'operando di origine è un riferimento a un oggetto incompatibile, viene generata un'eccezione System.InvalidCastException
.
10.3.8 Conversioni esplicite che coinvolgono parametri di tipo
Per un T
noto come tipo riferimento (§15.2.5), esistono le conversioni di riferimento esplicite seguenti (§10.3.5):
- Dalla classe
C
base effettiva diT
aT
e da qualsiasi classe di base diC
aT
. - Da qualsiasi interface_type a
T
. - Da
T
a qualsiasi interface_typeI
purché non sia già presente una conversione di riferimento implicita daT
aI
. - Da un
U
aT
condizione cheT
dipende daU
(§15.2.5).Nota: poiché
T
è noto come tipo riferimento, nell'ambito diT
, il tipo di runtime di sarà sempre un tipo riferimento, anche seU
non è noto come tipo riferimento in fase di compilazione. nota finale
Per un T
che non è noto come tipo di riferimento (§15.2.5), le conversioni seguenti che coinvolgono T
sono considerate conversioni unboxing (§10.3.7) in fase di compilazione. In fase di esecuzione, se T
è un tipo di valore, la conversione viene eseguita come conversione unboxing. In fase di esecuzione, se T
è un tipo di riferimento, la conversione viene eseguita come conversione esplicita dei riferimenti o conversione di identità.
- Dalla classe
C
base effettiva diT
aT
e da qualsiasi classe di base diC
aT
.Nota: C sarà uno dei tipi
System.Object
,System.ValueType
oSystem.Enum
(in caso contrarioT
sarebbe noto come tipo riferimento). nota finale - Da qualsiasi interface_type a
T
.
Per un type_parameterT
che non è noto come tipo riferimento (§15.2.5), esistono le conversioni esplicite seguenti:
- Da
T
a qualsiasi interface_typeI
fornito non esiste già una conversione implicita daT
aI
. Questa conversione è costituita da una conversione boxing implicita (§10.2.9) daT
aobject
seguita da una conversione di riferimento esplicita daobject
aI
. In fase di esecuzione, seT
è un tipo di valore, la conversione viene eseguita come conversione boxing seguita da una conversione di riferimento esplicita. In fase di esecuzione, seT
è un tipo riferimento, la conversione viene eseguita come conversione di riferimento esplicita. - Da un parametro
U
di tipo aT
specificato cheT
dipende daU
(§15.2.5). In fase di esecuzione, seT
è un tipo valore eU
è un tipo riferimento, la conversione viene eseguita come conversione unboxing. In fase di esecuzione, se eT
U
sono tipi valore,T
eU
sono necessariamente lo stesso tipo e non viene eseguita alcuna conversione. In fase di esecuzione, seT
è un tipo riferimento,U
è necessariamente anche un tipo riferimento e la conversione viene eseguita come conversione esplicita dei riferimenti o conversione di identità.
In tutti i casi, le regole assicurano che una conversione venga eseguita come conversione unboxing se e solo se in fase di esecuzione la conversione proviene da un tipo riferimento a un tipo valore.
Le regole precedenti non consentono una conversione esplicita diretta da un parametro di tipo non vincolato a un tipo non di interfaccia, che potrebbe essere sorprendente. Il motivo di questa regola è evitare confusione e rendere chiara la semantica di tali conversioni.
Esempio: si consideri la dichiarazione seguente:
class X<T> { public static long F(T t) { return (long)t; // Error } }
Se la conversione esplicita diretta di
t
inlong
è consentita, si potrebbe facilmente aspettarsi cheX<int>.F(7)
restituisca7L
. Tuttavia, non lo sarebbe, perché le conversioni numeriche standard vengono considerate solo quando i tipi sono noti come numerici in fase di associazione. Per rendere chiara la semantica, l'esempio precedente deve invece essere scritto:class X<T> { public static long F(T t) { return (long)(object)t; // Ok, but will only work when T is long } }
Questo codice verrà ora compilato ma in esecuzione
X<int>.F(7)
genererà quindi un'eccezione in fase di esecuzione, poiché un boxedint
non può essere convertito direttamente in un oggettolong
.esempio finale
10.3.9 Conversioni esplicite definite dall'utente
Una conversione esplicita definita dall'utente è costituita da una conversione esplicita standard facoltativa, seguita dall'esecuzione di un operatore di conversione implicito o esplicito definito dall'utente, seguito da un'altra conversione esplicita standard facoltativa. Le regole esatte per la valutazione delle conversioni esplicite definite dall'utente sono descritte in §10.5.5.
10.4 Conversioni standard
10.4.1 Generale
Le conversioni standard sono quelle conversioni predefinite che possono verificarsi come parte di una conversione definita dall'utente.
10.4.2 Conversioni implicite standard
Le conversioni implicite seguenti vengono classificate come conversioni implicite standard:
- Conversioni di identità (§10.2.2)
- Conversioni numeriche implicite (§10.2.3)
- Conversioni nullable implicite (§10.2.6)
- Conversioni di valori letterali Null (§10.2.7)
- Conversioni di riferimenti implicite (§10.2.8)
- Conversioni boxing (§10.2.9)
- Conversioni implicite di espressioni costanti (§10.2.11)
- Conversioni implicite che coinvolgono parametri di tipo (§10.2.12)
Le conversioni implicite standard escludono in modo specifico conversioni implicite definite dall'utente.
10.4.3 Conversioni esplicite standard
Le conversioni esplicite standard sono tutte conversioni implicite standard più il subset delle conversioni esplicite per le quali esiste una conversione implicita standard opposta.
Nota: se esiste una conversione implicita standard da un tipo a un tipo
A
B
, esiste una conversione esplicita standard dal tipoA
al tipoB
e dal tipoB
al tipoA
. nota finale
10.5 Conversioni definite dall'utente
10.5.1 Generale
C# consente l'aumento delle conversioni implicite ed esplicite predefinite tramite conversioni definite dall'utente. Le conversioni definite dall'utente vengono introdotte dichiarando gli operatori di conversione (§15.10.4) nei tipi di classe e struct.
10.5.2 Conversioni definite dall'utente consentite
C# consente di dichiarare solo determinate conversioni definite dall'utente. In particolare, non è possibile ridefinire una conversione implicita o esplicita già esistente.
Per un tipo di origine e un tipo S
di T
destinazione specificati, se S
o T
sono tipi valore nullable, consentire S₀
e T₀
fare riferimento ai relativi tipi sottostanti, in caso contrario S₀
e T₀
sono uguali rispettivamente a S
e T
. Una classe o uno struct è autorizzato a dichiarare una conversione da un tipo di origine a un tipo S
di T
destinazione solo se sono soddisfatte tutte le condizioni seguenti:
-
S₀
eT₀
sono tipi diversi. -
S₀
OppureT₀
è il tipo di classe o struct in cui viene eseguita la dichiarazione dell'operatore. - Né
S₀
néT₀
è un interface_type. - Escluse le conversioni definite dall'utente, una conversione non esiste da
S
aT
o daT
aS
.
Le restrizioni applicabili alle conversioni definite dall'utente sono specificate in §15.10.4.
10.5.3 Valutazione delle conversioni definite dall'utente
Una conversione definita dall'utente converte un'espressione di origine, che può avere un tipo di origine, in un altro tipo, denominato tipo di destinazione. Valutazione di un centro di conversione definito dall'utente per trovare l'operatore di conversione definito dall'utente più specifico per l'espressione di origine e il tipo di destinazione. Questa determinazione è suddivisa in diversi passaggi:
- Ricerca del set di classi e struct da cui verranno considerati gli operatori di conversione definiti dall'utente. Questo set è costituito dal tipo di origine e dalle relative classi base, se il tipo di origine esiste, insieme al tipo di destinazione e alle relative classi di base. A questo scopo si presuppone che solo le classi e gli struct possano dichiarare operatori definiti dall'utente e che i tipi non di classe non abbiano classi di base. Inoltre, se il tipo di origine o di destinazione è un tipo nullable-value-type, viene invece usato il tipo sottostante.
- Da tale set di tipi, determinare quali operatori di conversione definiti dall'utente e lifted sono applicabili. Affinché un operatore di conversione sia applicabile, è possibile eseguire una conversione standard (§10.4) dall'espressione di origine al tipo di operando dell'operatore ed è possibile eseguire una conversione standard dal tipo di risultato dell'operatore al tipo di destinazione.
- Dal set di operatori definiti dall'utente applicabili, determinare l'operatore senza ambiguità più specifico. In generale, l'operatore più specifico è l'operatore il cui tipo di operando è "più vicino" all'espressione di origine e il cui tipo di risultato è "più vicino" al tipo di destinazione. Gli operatori di conversione definiti dall'utente sono preferiti rispetto agli operatori di conversione lifted. Le regole esatte per stabilire l'operatore di conversione definito dall'utente più specifico sono definite nelle sottoclause seguenti.
Dopo aver identificato un operatore di conversione definito dall'utente più specifico, l'esecuzione effettiva della conversione definita dall'utente prevede fino a tre passaggi:
- Prima di tutto, se necessario, eseguire una conversione standard dall'espressione di origine al tipo di operando dell'operatore di conversione definito dall'utente o lifted.
- Richiamare quindi l'operatore di conversione lifted o definito dall'utente per eseguire la conversione.
- Infine, se necessario, eseguire una conversione standard dal tipo di risultato dell'operatore di conversione definito dall'utente al tipo di destinazione.
La valutazione di una conversione definita dall'utente non comporta mai più di un operatore di conversione definito dall'utente o lifted. In altre parole, una conversione dal tipo al tipo S
T
non eseguirà mai una conversione definita dall'utente da a S
e quindi eseguirà una conversione definita dall'utente da X
X
a T
.
- Le definizioni esatte della valutazione delle conversioni implicite o esplicite definite dall'utente vengono fornite nelle sottoclause seguenti. Le definizioni usano i termini seguenti:
- Se esiste una conversione implicita standard (§10.4.2) da un tipo a un tipo
A
B
e se nessunoA
o sonoB
s
,A
viene detto che èB
eB
viene detto che include .A
- Se esiste una conversione implicita standard (§10.4.2
E
- Il tipo più incomprensivo in un set di tipi è quello che include tutti gli altri tipi nel set. Se nessun singolo tipo include tutti gli altri tipi, il set non ha alcun tipo più incomprensivo. In termini più intuitivi, il tipo più incomprensibile è il tipo "più grande" nel set, ovvero quello in cui ognuno degli altri tipi può essere convertito in modo implicito.
- Il tipo più incluso in un set di tipi è quello incluso in tutti gli altri tipi del set. Se nessun singolo tipo è incluso in tutti gli altri tipi, il set non ha alcun tipo più incluso. In termini più intuitivi, il tipo più incluso è il tipo "più piccolo" nel set, ovvero quello che può essere convertito in modo implicito in ognuno degli altri tipi.
10.5.4 Conversioni implicite definite dall'utente
Una conversione implicita definita dall'utente da un'espressione E
a un tipo T
viene elaborata come segue:
Determinare i tipi
S
eS₀
T₀
.- Se
E
ha un tipo, lasciareS
che sia quel tipo. - Se
S
oT
sono tipi valore nullable, lasciareSᵢ
eTᵢ
essere i relativi tipi sottostanti, in caso contrario lasciareSᵢ
eTᵢ
essereS
rispettivamente eT
. - Se
Sᵢ
oTᵢ
sono parametri di tipo, lasciareS₀
eT₀
essere le relative classi di base valide, in caso contrario lasciareS₀
eT₀
essereSₓ
rispettivamente eTᵢ
.
- Se
Trovare il set di tipi,
D
, da cui verranno considerati gli operatori di conversione definiti dall'utente. Questo set è costituito daS₀
(se esistente ed è una classe o uno struct), dalle classi di base diS₀
(seS₀
S₀
esistente ed è una classe) eT₀
(seT₀
è una classe o uno struct). Al setD
viene aggiunto un tipo solo se non esiste una conversione di identità in un altro tipo già incluso nel set.Trovare il set di operatori di conversione definiti dall'utente e lifted applicabili,
U
. Questo set è costituito dagli operatori di conversione impliciti definiti dall'utente e lifted dichiarati dalle classi o dagli struct inD
che converte da un tipo che includeE
a un tipo incluso daT
. SeU
è vuoto, la conversione non è definita e si verifica un errore in fase di compilazione.- Se
S
esiste e uno degli operatori nellaU
conversione daS
,Sₓ
èS
. - In caso contrario,
Sₓ
è il tipo più incluso nel set combinato di tipi di origine degli operatori inU
. Se non è possibile trovare esattamente un tipo più incluso, la conversione è ambigua e si verifica un errore in fase di compilazione.
- Se
Trovare il tipo di destinazione più specifico,
Tₓ
, degli operatori inU
:- Se uno degli operatori in converte in
U
T
,Tₓ
èT
. - In caso contrario,
Tₓ
è il tipo più incluso nel set combinato di tipi di destinazione degli operatori inU
. Se non è possibile trovare esattamente uno dei tipi più inclusi, la conversione è ambigua e si verifica un errore in fase di compilazione.
- Se uno degli operatori in converte in
Trovare l'operatore di conversione più specifico:
- Se
U
contiene esattamente un operatore di conversione definito dall'utente che esegue la conversione daSₓ
aTₓ
, questo è l'operatore di conversione più specifico. - In caso contrario, se
U
contiene esattamente un operatore di conversione lifted che converte daSₓ
aTₓ
, questo è l'operatore di conversione più specifico. - In caso contrario, la conversione è ambigua e si verifica un errore in fase di compilazione.
- Se
Infine, applicare la conversione:
- Se E non dispone già del tipo
Sₓ
, viene eseguita una conversione implicita standard daE
aSₓ
. - L'operatore di conversione più specifico viene richiamato per eseguire la conversione da
Sₓ
aTₓ
. - Se
Tₓ
nonT
è , viene eseguita una conversione implicita standard daTₓ
aT
.
- Se E non dispone già del tipo
Esiste una conversione implicita definita dall'utente da un tipo S
a un tipo T
se esiste una conversione implicita definita dall'utente da una variabile di tipo S
a T
.
10.5.5 Conversioni esplicite definite dall'utente
Una conversione esplicita definita dall'utente da un'espressione E
a un tipo T
viene elaborata come segue:
- Determinare i tipi
S
eS₀
T₀
.- Se
E
ha un tipo, lasciareS
che sia quel tipo. - Se
S
oT
sono tipi valore nullable, lasciareSᵢ
eTᵢ
essere i relativi tipi sottostanti, in caso contrario lasciareSᵢ
eTᵢ
essereS
rispettivamente eT
. - Se
Sᵢ
oTᵢ
sono parametri di tipo, lasciareS₀
eT₀
essere le relative classi di base valide, in caso contrario lasciareS₀
eT₀
essereSᵢ
rispettivamente eTᵢ
.
- Se
- Trovare il set di tipi,
D
, da cui verranno considerati gli operatori di conversione definiti dall'utente. Questo set è costituito daS₀
(seS₀
esistente ed è una classe o uno struct), dalle classi di base diS₀
(seS₀
esistente ed è una classe),T₀
(seT₀
è una classe o uno struct) e dalle classi base diT₀
(seT₀
è una classe).A
il tipo viene aggiunto al setD
solo se non esiste una conversione di identità in un altro tipo già incluso nel set. - Trovare il set di operatori di conversione definiti dall'utente e lifted applicabili,
U
. Questo set è costituito dagli operatori di conversione impliciti o espliciti definiti dall'utente dichiarati dalle classi o dagli struct inD
che converte da un tipo che includeE
o include (S
se esistente) a un tipo che include o include .T
SeU
è vuoto, la conversione non è definita e si verifica un errore in fase di compilazione. - Trovare il tipo di origine più specifico,
Sₓ
, degli operatori inU
:- Se S esiste e uno degli operatori in
U
convert daS
,Sₓ
èS
. - In caso contrario, se uno degli operatori in
U
converte da tipi che includonoE
,Sₓ
è il tipo più incluso nel set combinato di tipi di origine di tali operatori. Se non è possibile trovare alcun tipo più incluso, la conversione è ambigua e si verifica un errore in fase di compilazione. - In caso contrario,
Sₓ
è il tipo più incluso nel set combinato di tipi di origine degli operatori inU
. Se non è possibile trovare esattamente uno dei tipi più inclusi, la conversione è ambigua e si verifica un errore in fase di compilazione.
- Se S esiste e uno degli operatori in
- Trovare il tipo di destinazione più specifico,
Tₓ
, degli operatori inU
:- Se uno degli operatori in converte in
U
T
,Tₓ
èT
. - In caso contrario, se uno degli operatori in
U
converte in tipi inclusi inT
,Tₓ
è il tipo più incluso nel set combinato di tipi di destinazione di tali operatori. Se non è possibile trovare esattamente uno dei tipi più inclusi, la conversione è ambigua e si verifica un errore in fase di compilazione. - In caso contrario,
Tₓ
è il tipo più incluso nel set combinato di tipi di destinazione degli operatori inU
. Se non è possibile trovare alcun tipo più incluso, la conversione è ambigua e si verifica un errore in fase di compilazione.
- Se uno degli operatori in converte in
- Trovare l'operatore di conversione più specifico:
- Se U contiene esattamente un operatore di conversione definito dall'utente che esegue la conversione da
Sₓ
aTₓ
, questo è l'operatore di conversione più specifico. - In caso contrario, se
U
contiene esattamente un operatore di conversione lifted che converte daSₓ
aTₓ
, questo è l'operatore di conversione più specifico. - In caso contrario, la conversione è ambigua e si verifica un errore in fase di compilazione.
- Se U contiene esattamente un operatore di conversione definito dall'utente che esegue la conversione da
- Infine, applicare la conversione:
- Se
E
non ha già il tipoSₓ
, viene eseguita una conversione esplicita standard da E aSₓ
. - L'operatore di conversione definito dall'utente più specifico viene richiamato per eseguire la conversione da
Sₓ
aTₓ
. - Se
Tₓ
nonT
è , viene eseguita una conversione esplicita standard daTₓ
aT
.
- Se
Esiste una conversione esplicita definita dall'utente da un tipo S
a un tipo T
se esiste una conversione esplicita definita dall'utente da una variabile di tipo S
a T
.
10.6 Conversioni che coinvolgono tipi nullable
10.6.1 Conversioni nullable
Le conversioni nullable consentono conversioni predefinite che operano su tipi valore non nullable da usare anche con forme nullable di tali tipi. Per ognuna delle conversioni implicite o esplicite predefinite che vengono convertite da un tipo di valore non nullable a un tipo S
di T
valore non nullable (§10.2.2, §10.2.3, §10.2.4, §10.2.11, §10.3.2 e §10.3.3), esistono le conversioni nullable seguenti:
- Conversione implicita o esplicita da
S?
aT?
- Conversione implicita o esplicita da
S
aT?
- Conversione esplicita da
S?
aT
.
Una conversione nullable viene classificata come conversione implicita o esplicita.
Alcune conversioni nullable vengono classificate come conversioni standard e possono verificarsi come parte di una conversione definita dall'utente. In particolare, tutte le conversioni implicite nullable vengono classificate come conversioni implicite standard (§10.4.2) e quelle esplicite nullable che soddisfano i requisiti di §10.4.3 vengono classificate come conversioni esplicite standard.
Valutazione di una conversione nullable basata su una conversione sottostante da S
a T
procede come indicato di seguito:
- Se la conversione nullable è da
S?
aT?
:- Se il valore di origine è null (
HasValue
la proprietà èfalse
), il risultato è il valore Null di tipoT?
. - In caso contrario, la conversione viene valutata come un annullamento del wrapping da
S?
aS
, seguita dalla conversione sottostante da aS
, seguita da un wrapping daT
T
aT?
.
- Se il valore di origine è null (
- Se la conversione nullable è da
S
aT?
, la conversione viene valutata come conversione sottostante daS
aT
seguita da un wrapping daT
aT?
. - Se la conversione nullable è da
S?
aT
, la conversione viene valutata come un annullamento del wrapping daS?
aS
seguito della conversione sottostante daS
aT
.
10.6.2 Conversioni lifted
Dato un operatore di conversione definito dall'utente che esegue la conversione da un tipo di S
valore non nullable a un tipo valore T
non nullable, esiste un operatore di conversione lifted che converte da S?
a T?
. Questo operatore di conversione lifted esegue un unwrapping da S?
a S
seguito della conversione definita dall'utente da a S
seguito di un wrapping da T
T
a T?
, ad eccezione del fatto che un valore S?
Null converte direttamente in un valore T?
Null. Un operatore di conversione lifted ha la stessa classificazione implicita o esplicita dell'operatore di conversione definito dall'utente sottostante.
10.7 Conversioni di funzioni anonime
10.7.1 Generale
Un anonymous_method_expression o un lambda_expression è classificato come funzione anonima (§12.19). L'espressione non dispone di un tipo, ma può essere convertita in modo implicito in un tipo delegato compatibile. Alcune espressioni lambda possono anche essere convertite in modo implicito in un tipo di albero delle espressioni compatibile.
In particolare, una funzione F
anonima è compatibile con un tipo D
delegato fornito:
- Se
F
contiene un anonymous_function_signature,D
eF
avere lo stesso numero di parametri. - Se
F
non contiene un anonymous_function_signature,D
può avere zero o più parametri di qualsiasi tipo, purché nessun parametro di sia un parametro diD
output. - Se
F
ha un elenco di parametri tipizzato in modo esplicito, ogni parametro inD
ha gli stessi modificatori del parametro corrispondente inF
e esiste una conversione identity tra il parametro corrispondente inF
. - Se
F
ha un elenco di parametri tipizzato in modo implicito,D
non dispone di parametri di riferimento o di output. - Se il corpo di
F
è un'espressione eD
ha un tipo restituito void oF
è asincrono eD
ha un«TaskType»
tipo restituito (§15.15.1), quando a ogni parametro vieneF
assegnato il tipo del parametro corrispondente inD
, il corpo diF
è un'espressione valida (w.r.t §12) che sarebbe consentita come statement_expression (§13.7). - Se il corpo di
F
è un blocco eD
ha un tipo restituito void oF
è asincrono eD
ha un«TaskType»
tipo restituito , quando a ogni parametro diF
viene assegnato il tipo del parametro corrispondente inD
, il corpo diF
è un blocco valido (w.r.t §13.3) in cui nessunareturn
istruzione specifica un'espressione. - Se il corpo di
F
è un'espressione eF
non è asincrono eD
ha un tipo nonvoid
restituito o«TaskType»<T>
restituito (§15.15.1), quando a ogni parametro vieneF
assegnato il tipo del parametro corrispondente in , il corpo diD
è un'espressione valida (w.r.tF
) che è implicitamente convertibile in .T
- Se il corpo di
F
è un blocco eF
non è asincrono eD
ha un tipo restituito non void oppureT
«TaskType»<T>
restituito, quando a ogni parametro vieneF
assegnato il tipo del parametro corrispondente inD
, il corpo di è un blocco diF
istruzioni valido (w.r.t §13.3) con un punto finale non raggiungibile in cui ogni istruzione return specifica un'espressione che è implicitamente convertibileT
in .
Esempio: gli esempi seguenti illustrano queste regole:
delegate void D(int x); D d1 = delegate { }; // Ok D d2 = delegate() { }; // Error, signature mismatch D d3 = delegate(long x) { }; // Error, signature mismatch D d4 = delegate(int x) { }; // Ok D d5 = delegate(int x) { return; }; // Ok D d6 = delegate(int x) { return x; }; // Error, return type mismatch delegate void E(out int x); E e1 = delegate { }; // Error, E has an output parameter E e2 = delegate(out int x) { x = 1; }; // Ok E e3 = delegate(ref int x) { x = 1; }; // Error, signature mismatch delegate int P(params int[] a); P p1 = delegate { }; // Error, end of block reachable P p2 = delegate { return; }; // Error, return type mismatch P p3 = delegate { return 1; }; // Ok P p4 = delegate { return "Hello"; }; // Error, return type mismatch P p5 = delegate(int[] a) // Ok { return a[0]; }; P p6 = delegate(params int[] a) // Error, params modifier { return a[0]; }; P p7 = delegate(int[] a) // Error, return type mismatch { if (a.Length > 0) return a[0]; return "Hello"; }; delegate object Q(params int[] a); Q q1 = delegate(int[] a) // Ok { if (a.Length > 0) return a[0]; return "Hello"; };
esempio finale
Esempio: gli esempi seguenti usano un tipo
Func<A,R>
delegato generico che rappresenta una funzione che accetta un argomento di tipo e restituisce un valore di tipoA
R
:delegate R Func<A,R>(A arg);
Nelle assegnazioni
Func<int,int> f1 = x => x + 1; // Ok Func<int,double> f2 = x => x + 1; // Ok Func<double,int> f3 = x => x + 1; // Error Func<int, Task<int>> f4 = async x => x + 1; // Ok
Il parametro e i tipi restituiti di ogni funzione anonima vengono determinati dal tipo della variabile a cui viene assegnata la funzione anonima.
La prima assegnazione converte correttamente la funzione anonima nel tipo
Func<int,int>
delegato perché, quandox
viene specificato il tipo ,int
è un'espressione valida convertibile in modo implicito nel tipox + 1
int
.Analogamente, la seconda assegnazione converte correttamente la funzione anonima nel tipo delegato Func<int, double> perché il risultato di
x + 1
(di tipoint
) è convertibile in modo implicito nel tipodouble
.Tuttavia, la terza assegnazione è un errore in fase di compilazione perché, quando
x
viene specificato il tipodouble
, il risultato dix + 1
(di tipodouble
) non è convertibile in modo implicito nel tipoint
.La quarta assegnazione converte correttamente la funzione asincrona anonima nel tipo
Func<int, Task<int>>
delegato perché il risultato dix + 1
(di tipoint
) è convertibile in modo implicito nel tipoint
restituito effettivo dell'espressione lambda asincrona, che ha un tipoTask<int>
restituito .esempio finale
Un'espressione F
lambda è compatibile con un tipo di Expression<D>
albero delle espressioni se F
è compatibile con il tipo D
delegato . Ciò non si applica ai metodi anonimi, ma solo alle espressioni lambda.
Le funzioni anonime possono influenzare la risoluzione dell'overload e partecipare all'inferenza del tipo. Per altri dettagli, vedere §12.6 .
10.7.2 Valutazione delle conversioni di funzioni anonime in tipi delegati
La conversione di una funzione anonima in un tipo delegato produce un'istanza del delegato che fa riferimento alla funzione anonima e al set (possibilmente vuoto) di variabili esterne acquisite attive al momento della valutazione. Quando viene richiamato il delegato, viene eseguito il corpo della funzione anonima. Il codice nel corpo viene eseguito usando il set di variabili esterne acquisite a cui fa riferimento il delegato. Un delegate_creation_expression (§12.8.17.6) può essere usato come sintassi alternativa per convertire un metodo anonimo in un tipo delegato.
L'elenco chiamate di un delegato generato da una funzione anonima contiene una singola voce. L'oggetto di destinazione esatto e il metodo di destinazione del delegato non sono specificati. In particolare, non è specificato se l'oggetto di destinazione del delegato è null
, il this
valore del membro della funzione di inclusione o un altro oggetto.
Le conversioni di funzioni anonime identiche semanticamente con lo stesso set (possibilmente vuoto) di istanze di variabili esterne acquisite negli stessi tipi delegati sono consentite (ma non necessarie) di restituire la stessa istanza del delegato. Il termine semanticamente identico viene usato qui per indicare che l'esecuzione delle funzioni anonime, in tutti i casi, produce gli stessi effetti in base agli stessi argomenti. Questa regola consente l'ottimizzazione del codice, ad esempio il codice seguente.
delegate double Function(double x);
class Test
{
static double[] Apply(double[] a, Function f)
{
double[] result = new double[a.Length];
for (int i = 0; i < a.Length; i++)
{
result[i] = f(a[i]);
}
return result;
}
static void F(double[] a, double[] b)
{
a = Apply(a, (double x) => Math.Sin(x));
b = Apply(b, (double y) => Math.Sin(y));
...
}
}
Poiché i due delegati di funzione anonima hanno lo stesso set (vuoto) di variabili dell'ambiente esterno acquisite e poiché le funzioni anonime sono semanticamente identiche, ai delegati è permesso di riferirsi allo stesso metodo di destinazione. In effetti, un compilatore può restituire la stessa istanza del delegato da entrambe le espressioni di funzione anonime.
10.7.3 Valutazione delle conversioni di espressioni lambda in tipi di albero delle espressioni
La conversione di un'espressione lambda in un tipo di albero delle espressioni produce un albero delle espressioni (§8.6). Più precisamente, la valutazione della conversione dell'espressione lambda produce una struttura oggetto che rappresenta la struttura dell'espressione lambda stessa.
Non tutte le espressioni lambda possono essere convertite in tipi di albero delle espressioni. La conversione in un tipo delegato compatibile esiste sempre, ma può non riuscire in fase di compilazione per motivi definiti dall'implementazione.
Nota: i motivi comuni per cui un'espressione lambda non riesce a eseguire la conversione in un tipo di albero delle espressioni includono:
- Ha un corpo del blocco
- Ha il
async
modificatore- Contiene un operatore di assegnazione
- Contiene un parametro di output o riferimento
- Contiene un'espressione associata dinamicamente
nota finale
Conversioni dei gruppi di metodi 10.8
Esiste una conversione implicita da un gruppo di metodi (§12.2) a un tipo delegato compatibile (§20.4). Se D
è un tipo delegato ed E
è un'espressione classificata come gruppo di metodi, D
è compatibile con E
se e solo se E
contiene almeno un metodo applicabile nel formato normale (§12.6.4.2) a qualsiasi elenco di argomenti (§12.6.2) con tipi e modificatori corrispondenti ai tipi e modificatori di D
, come descritto di seguito.
L'applicazione in fase di compilazione della conversione da un gruppo E
di metodi a un tipo D
delegato è descritta di seguito.
- Viene selezionato un singolo metodo
M
corrispondente a una chiamata al metodo (§12.8.10.2) del moduloE(A)
, con le modifiche seguenti:- L'elenco
A
di argomenti è un elenco di espressioni, ognuna classificata come variabile e con il tipo e il modificatore (in
,out
oref
) del parametro corrispondente nella parameter_list di , ad eccezione deiD
parametri di tipodynamic
, dove l'espressione corrispondente ha il tipoobject
anzichédynamic
. - I metodi candidati considerati sono solo i metodi applicabili nella forma normale e non omettono parametri facoltativi (§12.6.4.2). Pertanto, i metodi candidati vengono ignorati se sono applicabili solo nel formato espanso o se uno o più dei relativi parametri facoltativi non dispongono di un parametro corrispondente in
D
.
- L'elenco
- Una conversione viene considerata esistente se l'algoritmo di §12.8.10.2 produce un singolo metodo
M
migliore compatibile (§20.4) conD
. - Se il metodo selezionato è un metodo
M
di istanza, l'espressione di istanza associata aE
determina l'oggetto di destinazione del delegato. - Se il metodo selezionato è un metodo
M
di estensione indicato tramite l'accesso a un membro in un'espressione di istanza, tale espressione di istanza determina l'oggetto di destinazione del delegato. - Il risultato della conversione è un valore di tipo
D
, ovvero un delegato che fa riferimento al metodo selezionato e all'oggetto di destinazione.
Esempio: di seguito vengono illustrate le conversioni dei gruppi di metodi:
delegate string D1(object o); delegate object D2(string s); delegate object D3(); delegate string D4(object o, params object[] a); delegate string D5(int i); class Test { static string F(object o) {...} static void G() { D1 d1 = F; // Ok D2 d2 = F; // Ok D3 d3 = F; // Error – not applicable D4 d4 = F; // Error – not applicable in normal form D5 d5 = F; // Error – applicable but not compatible } }
L'assegnazione a
d1
converte in modo implicito il gruppoF
di metodi in un valore di tipoD1
.L'assegnazione a
d2
mostra come è possibile creare un delegato a un metodo con tipi di parametro meno derivati (controvarianti) e un tipo restituito più derivato (covariante).L'assegnazione a
d3
mostra come non esiste alcuna conversione se il metodo non è applicabile.L'assegnazione a
d4
mostra come il metodo deve essere applicabile nella forma normale.L'assegnazione a
d5
mostra come i tipi di parametro e restituiti del delegato e del metodo possono differire solo per i tipi di riferimento.esempio finale
Come per tutte le altre conversioni implicite ed esplicite, l'operatore cast può essere usato per eseguire in modo esplicito una particolare conversione.
Esempio: di conseguenza, l'esempio
object obj = new EventHandler(myDialog.OkClick);
potrebbe invece essere scritto
object obj = (EventHandler)myDialog.OkClick;
esempio finale
Una conversione di un gruppo di metodi può fare riferimento a un metodo generico, specificando in modo esplicito argomenti di tipo all'interno E
di o tramite inferenza del tipo (§12.6.3). Se si usa l'inferenza del tipo, i tipi di parametro del delegato vengono usati come tipi di argomento nel processo di inferenza. Il tipo restituito del delegato non viene usato per l'inferenza. Se gli argomenti di tipo vengono specificati o dedotti, fanno parte del processo di conversione del gruppo di metodi; si tratta degli argomenti di tipo utilizzati per richiamare il metodo di destinazione quando viene richiamato il delegato risultante.
Esempio:
delegate int D(string s, int i); delegate int E(); class X { public static T F<T>(string s, T t) {...} public static T G<T>() {...} static void Main() { D d1 = F<int>; // Ok, type argument given explicitly D d2 = F; // Ok, int inferred as type argument E e1 = G<int>; // Ok, type argument given explicitly E e2 = G; // Error, cannot infer from return type } }
esempio finale
I gruppi di metodi possono influenzare la risoluzione dell'overload e partecipare all'inferenza dei tipi. Per altri dettagli, vedere §12.6 .
La valutazione in fase di esecuzione di una conversione di un gruppo di metodi procede come segue:
- Se il metodo selezionato in fase di compilazione è un metodo di istanza o è un metodo di estensione a cui si accede come metodo di istanza, l'oggetto di destinazione del delegato viene determinato dall'espressione di istanza associata a
E
:- L'espressione di istanza viene valutata. Se questa valutazione causa un'eccezione, non vengono eseguiti altri passaggi.
- Se l'espressione di istanza è di un reference_type, il valore calcolato dall'espressione di istanza diventa l'oggetto di destinazione. Se il metodo selezionato è un metodo di istanza e l'oggetto di destinazione è
null
, viene generata un'eccezioneSystem.NullReferenceException
e non vengono eseguiti altri passaggi. - Se l'espressione di istanza è di un value_type, viene eseguita un'operazione boxing (§10.2.9) per convertire il valore in un oggetto e questo oggetto diventa l'oggetto di destinazione.
- In caso contrario, il metodo selezionato fa parte di una chiamata al metodo statico e l'oggetto di destinazione del delegato è
null
. - Un'istanza del delegato di tipo
D
delegato viene ottenuta con un riferimento al metodo determinato in fase di compilazione e un riferimento all'oggetto di destinazione calcolato in precedenza, come indicato di seguito:- La conversione è consentita (ma non necessaria) per usare un'istanza di delegato esistente che contiene già questi riferimenti.
- Se un'istanza esistente non è stata riutilizzata, ne viene creata una nuova (§20.5). Se non è disponibile memoria sufficiente per allocare la nuova istanza, viene generata un'eccezione
System.OutOfMemoryException
. In caso contrario, l'istanza viene inizializzata con i riferimenti specificati.
ECMA C# draft specification