Condividi tramite


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 tipo long è implicita, pertanto le espressioni di tipo int possono essere considerate in modo implicito come tipo long. La conversione opposta, dal tipo al tipo longint, è 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 e T, per qualsiasi tipo T.
  • Tra T e T? per qualsiasi tipo di Triferimento .
  • Tra object e dynamic.
  • 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 oggetto t2.t3intstring I tipi di elemento tupla possono essere usati da tuple, come in t4, t5e t6. 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 tuple t4, t5e t6.

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 a short, int, longfloat, double, o decimal.
  • Da byte a short, ushortint, uint, long, ulong, float, , doubleo decimal.
  • Da short a int, longfloat, , doubleo decimal.
  • Da ushort a int, uint, longulong, float, , doubleo decimal.
  • Da int a long, floatdouble, o decimal.
  • Da uint a long, ulongfloat, , doubleo decimal.
  • Da long a float, doubleo decimal.
  • Da ulong a float, doubleo decimal.
  • Da char a ushort, int, uintlong, ulong, float, , doubleo decimal.
  • Da float a double.

Le conversioni da int, uinto longulong 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.FormattableStringSystem.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 e dynamic.
  • Da qualsiasi class_typeS a qualsiasi class_typeT, fornito S è derivato da T.
  • Da qualsiasi T , fornito S implementa .T
  • Da qualsiasi interface_typeS a qualsiasi interface_typeT, fornito S è derivato da T.
  • Da un array_typeS con un tipo di Sᵢ elemento a un array_typeT con un tipo di Tᵢelemento , purché tutte le condizioni seguenti siano vere:
    • S e T differiscono solo in tipo di elemento. In altre parole, S e T hanno lo stesso numero di dimensioni.
    • Esiste una conversione di riferimento implicita da Sᵢ a Tᵢ.
  • Da un tipo di S[] matrice unidimensionale a System.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 da S a T.
  • 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 tipo T₀ di interfaccia o delegato ed T₀ è convertibile a varianza (§18.2.3.3) in T.
  • 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_typeI in modo che sia presente una conversione boxing dal non_nullable_value_type a un altro interface_typeI₀e I₀ 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₀e I₀ 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 SI, con una classe boxing denominata S_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 tipo S consiste ora nell'esecuzione dell'espressione new S_Boxing(v) e nella restituzione dell'istanza risultante come valore del tipo di destinazione della conversione. Di conseguenza, le istruzioni

S 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 tipo Sdi runtime e un controllo del tipo di runtime usando l'operatore is 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 seguente

struct 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 fare box in modo che il valore di p venga copiato. Era Point stato dichiarato invece un oggetto class , il valore 20 verrebbe restituito perché p e box 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 objecte 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 e i 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 di d(string) al tipo di destinazione. Viene trovata una conversione in string ma non in int.

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, sbytebyteshortushorto uint, purché il valore della ulong sia compreso nell'intervallo del tipo di destinazione.
  • Un constant_expression di tipo long può essere convertito in tipo ulong, 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 classe Cbase effettiva , da T a qualsiasi classe di base di Ce da T a qualsiasi interfaccia implementata da C.
  • Da T a un interface_typeI nel Tset di interfacce effettivo e da T a qualsiasi interfaccia di base di I.
  • Da T a un parametro U di tipo specificato che T dipende da U (§15.2.5).

    Nota: poiché T è noto come tipo riferimento, nell'ambito di T, il tipo di runtime di U sarà sempre un tipo riferimento, anche se U 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 classe Cbase effettiva , da T a qualsiasi classe di base di Ce da T a qualsiasi interfaccia implementata da C.

    Nota: C sarà uno dei tipi System.Object, System.ValueTypeo System.Enum (in caso contrario T sarebbe noto come tipo riferimento). nota finale

  • Da T a un interface_typeI nel Tset di interfacce effettivo e da T a qualsiasi interfaccia di base di I.

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 TU 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 Tdi tipo esistono altre conversioni implicite seguenti:

  • Da T a un tipo riferimento S se ha una conversione implicita in un tipo riferimento S₀ e S₀ ha una conversione identity in S. In fase di esecuzione, la conversione viene eseguita allo stesso modo della conversione in S₀.
  • Da T a un tipo di I interfaccia se ha una conversione implicita in un tipo di I₀interfaccia ed I₀ è convertibile in varianza in I (§18.2.3.3). In fase di esecuzione, se T è 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 Ttipo 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 , t1t2 e t4 sono tutte valide, poiché esistono conversioni implicite dalle espressioni di t5elemento ai tipi di elemento corrispondenti. La dichiarazione di t3 non è valida perché non è presente alcuna conversione da null a int. La dichiarazione di t5 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 a byte, ushortuint, , ulongo char.
  • Da byte a sbyte o char.
  • Da short a sbyte, byte, ushortuint, ulong, o char.
  • Da ushort a sbyte, byteshort, o char.
  • Da int a sbyte, byte, shortushort, uint, , ulongo char.
  • Da uint a sbyte, byte, shortushort, int, o char.
  • Da long a sbyte, byte, shortushort, int, uint, , ulongo char.
  • Da ulong a sbyte, byte, shortushort, int, uint, , longo char.
  • Da char a sbyte, byteo short.
  • Da float a sbyte, byte, shortushort, int, uint, long, , ulong, charo decimal.
  • Da double a sbyte, byteshort, ushort, int, uint, long, ulong, , char, floato decimal.
  • Da decimal a sbyte, byteshort, ushort, int, uint, long, ulong, , char, floato double.

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'eccezione System.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
  • 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'eccezione System.OverflowException .
  • Per una conversione da float o double 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.
    • 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.
  • Per una conversione da double a float, il double valore viene arrotondato al valore più float vicino. Se il double valore è troppo piccolo per rappresentare come float, il risultato diventa zero con lo stesso segno del valore. Se la grandezza del double valore è troppo grande per rappresentare come , floatil risultato diventa infinito con lo stesso segno del valore. Se il double valore è NaN, il risultato è anche NaN.
  • Per una conversione da float o double in decimal, il valore di origine viene convertito in decimal 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 se decimal 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.
  • Per una conversione da decimal a float o double, il decimal valore viene arrotondato al valore o double 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 di float e double, ma non è garantito. Per decimal le rappresentazioni senza infiniti o valori NaN e con un intervallo minore di float, il risultato di una conversione da decimal a float o double non sarà mai infinito o NaN. nota finale

10.3.3 Conversioni esplicite di enumerazione

Le conversioni esplicite di enumerazione sono:

  • Da sbyte, byteshort, ushort, intuintlongulongcharfloat, doubleo decimal a qualsiasi enum_type.
  • Da qualsiasi enum_type a sbyte, byteshort, ushort, , int, uint, long, ulongcharfloat, 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_typeE con e il tipo sottostante di int, una conversione da E a byte viene elaborata come conversione numerica esplicita (§10.3.2) da a inte una conversione da bytebyte a E viene elaborata come conversione numerica implicita (§10.2.3) da byte a int. 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 , fornito S è una classe base di .T
  • Da qualsiasi T , fornito S non è sealed e non S implementa .T
  • Da qualsiasi T , fornito T non è bloccato o viene implementato .TS
  • Da qualsiasi interface_typeS a qualsiasi interface_typeT, specificato S non è derivato da T.
  • Da un array_typeS con un tipo di Sᵢ elemento a un array_typeT con un tipo di Tᵢelemento , purché tutte le condizioni seguenti siano vere:
    • S e T differiscono solo in tipo di elemento. In altre parole, S e T hanno lo stesso numero di dimensioni.
    • Esiste una conversione di riferimento esplicita da Sᵢ a Tᵢ.
  • Da System.Array e le interfacce implementate, a qualsiasi array_type.
  • Da un array_typeS[]a System.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 da S a T.
  • Da System.Collections.Generic.IList<S>, System.Collections.Generic.IReadOnlyList<S>e le relative interfacce di base a un tipo di T[]matrice unidimensionale , purché sia presente una conversione di identità o una conversione esplicita dei riferimenti da S a T.
  • Da System.Delegate e le interfacce implementate in qualsiasi delegate_type.
  • Da un tipo riferimento a un tipo S riferimento T se ha una conversione di riferimento esplicita da S a un tipo T₀ riferimento e T₀ viene eseguita una conversione identity da T₀ a T.
  • Da un tipo di riferimento a un'interfaccia S o a un tipo T delegato se è presente una conversione di riferimento esplicita da S a un tipo T₀ di interfaccia o delegato e può T₀ essere variance convertibile in T o T è convertibile T₀a §18.2.3.3.
  • Da D<S₁...Sᵥ> a D<T₁...Tᵥ> dove D<X₁...Xᵥ> è un tipo delegato generico, D<S₁...Sᵥ> non è compatibile con o identico a D<T₁...Tᵥ>e per ogni parametro Xᵢ di tipo dei D blocchi seguenti:
    • Se Xᵢ è invariante, Sᵢ è identico a Tᵢ.
    • Se Xᵢ è covariante, è presente una conversione di identità, la conversione implicita dei riferimenti o la conversione esplicita dei riferimenti da Sᵢ a Tᵢ.
    • Se Xᵢ è controvariante, Sᵢ e Tᵢ sono identici o entrambi i tipi di riferimento.
  • 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 nullo 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 Ttipo 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à da I a .I₀
  • Da qualsiasi interface_type a qualsiasi non_nullable_value_typeI in cui è presente una conversione unboxing da un interface_typeI₀ all'non_nullable_value_type ed I₀ è variance_convertible a I o I è convertibile I₀ 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 è nullo 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_typeS consiste nell'eseguire l'espressione ((S_Boxing)box).value. Di conseguenza, le istruzioni

object 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 nullSystem.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 di T a T e da qualsiasi classe di base di C a T.
  • Da qualsiasi interface_type a T.
  • Da T a qualsiasi interface_typeI purché non sia già presente una conversione di riferimento implicita da T a I.
  • Da un U a T condizione che T dipende da U (§15.2.5).

    Nota: poiché T è noto come tipo riferimento, nell'ambito di T, il tipo di runtime di sarà sempre un tipo riferimento, anche se U 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 di T a T e da qualsiasi classe di base di C a T.

    Nota: C sarà uno dei tipi System.Object, System.ValueTypeo System.Enum (in caso contrario T 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 da T a I. Questa conversione è costituita da una conversione boxing implicita (§10.2.9) da T a object seguita da una conversione di riferimento esplicita da object a I. In fase di esecuzione, se T è un tipo di valore, la conversione viene eseguita come conversione boxing seguita da una conversione di riferimento esplicita. In fase di esecuzione, se T è un tipo riferimento, la conversione viene eseguita come conversione di riferimento esplicita.
  • Da un parametro U di tipo a T specificato che T dipende da U (§15.2.5). In fase di esecuzione, se T è un tipo valore e U è un tipo riferimento, la conversione viene eseguita come conversione unboxing. In fase di esecuzione, se e TU 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 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 in long è consentita, si potrebbe facilmente aspettarsi che X<int>.F(7) restituisca 7L. 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 boxed int non può essere convertito direttamente in un oggetto long.

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 AB, esiste una conversione esplicita standard dal tipo A al tipo B e dal tipo B al tipo A. 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 Tdestinazione 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₀ e T₀ sono tipi diversi.
  • S₀ Oppure T₀ è il tipo di classe o struct in cui viene eseguita la dichiarazione dell'operatore.
  • S₀T₀ è un interface_type.
  • Escluse le conversioni definite dall'utente, una conversione non esiste da S a T o da T a S.

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 ST non eseguirà mai una conversione definita dall'utente da a S e quindi eseguirà una conversione definita dall'utente da XX 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 ABe se nessuno A o sono Bs, A viene detto che è B e B viene detto che include .A
  • Se esiste una conversione implicita standard (§10.4.2E
  • 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 Se S₀T₀.

    • Se E ha un tipo, lasciare S che sia quel tipo.
    • Se S o T sono tipi valore nullable, lasciare Sᵢ e Tᵢ essere i relativi tipi sottostanti, in caso contrario lasciare Sᵢ e Tᵢ essere S rispettivamente e T.
    • Se Sᵢ o Tᵢ sono parametri di tipo, lasciare S₀ e T₀ essere le relative classi di base valide, in caso contrario lasciare S₀ e T₀ essere Sₓ rispettivamente e Tᵢ.
  • Trovare il set di tipi, D, da cui verranno considerati gli operatori di conversione definiti dall'utente. Questo set è costituito da S₀ (se esistente ed è una classe o uno struct), dalle classi di base di S₀ (se S₀S₀ esistente ed è una classe) e T₀ (se T₀ è una classe o uno struct). Al set D 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 in D che converte da un tipo che include E a un tipo incluso da T. Se U è vuoto, la conversione non è definita e si verifica un errore in fase di compilazione.

    • Se S esiste e uno degli operatori nella U conversione da S, Sₓ è S.
    • In caso contrario, Sₓ è il tipo più incluso nel set combinato di tipi di origine degli operatori in U. Se non è possibile trovare esattamente un tipo più incluso, la conversione è ambigua e si verifica un errore in fase di compilazione.
  • Trovare il tipo di destinazione più specifico, Tₓ, degli operatori in U:

    • Se uno degli operatori in converte in UT, Tₓ è T.
    • In caso contrario, Tₓ è il tipo più incluso nel set combinato di tipi di destinazione degli operatori in U. Se non è possibile trovare esattamente uno dei tipi più inclusi, la conversione è ambigua e si verifica un errore in fase di compilazione.
  • Trovare l'operatore di conversione più specifico:

    • Se U contiene esattamente un operatore di conversione definito dall'utente che esegue la conversione da Sₓ a Tₓ, questo è l'operatore di conversione più specifico.
    • In caso contrario, se U contiene esattamente un operatore di conversione lifted che converte da Sₓ a Tₓ, questo è l'operatore di conversione più specifico.
    • In caso contrario, la conversione è ambigua e si verifica un errore in fase di compilazione.
  • Infine, applicare la conversione:

    • Se E non dispone già del tipo Sₓ, viene eseguita una conversione implicita standard da E a Sₓ .
    • L'operatore di conversione più specifico viene richiamato per eseguire la conversione da Sₓ a Tₓ.
    • Se Tₓ non Tè , viene eseguita una conversione implicita standard da Tₓ a T .

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 Se S₀T₀.
    • Se E ha un tipo, lasciare S che sia quel tipo.
    • Se S o T sono tipi valore nullable, lasciare Sᵢ e Tᵢ essere i relativi tipi sottostanti, in caso contrario lasciare Sᵢ e Tᵢ essere S rispettivamente e T.
    • Se Sᵢ o Tᵢ sono parametri di tipo, lasciare S₀ e T₀ essere le relative classi di base valide, in caso contrario lasciare S₀ e T₀ essere Sᵢ rispettivamente e Tᵢ.
  • Trovare il set di tipi, D, da cui verranno considerati gli operatori di conversione definiti dall'utente. Questo set è costituito da S₀ (se S₀ esistente ed è una classe o uno struct), dalle classi di base di S₀ (se S₀ esistente ed è una classe), T₀ (se T₀ è una classe o uno struct) e dalle classi base di T₀ (se T₀ è una classe). A il tipo viene aggiunto al set D 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 in D che converte da un tipo che include E o include (Sse esistente) a un tipo che include o include .T Se U è vuoto, la conversione non è definita e si verifica un errore in fase di compilazione.
  • Trovare il tipo di origine più specifico, Sₓ, degli operatori in U:
    • Se S esiste e uno degli operatori in U convert da S, Sₓ è S.
    • In caso contrario, se uno degli operatori in U converte da tipi che includono E, 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 in U. Se non è possibile trovare esattamente uno dei tipi più inclusi, la conversione è ambigua e si verifica un errore in fase di compilazione.
  • Trovare il tipo di destinazione più specifico, Tₓ, degli operatori in U:
    • Se uno degli operatori in converte in UT, Tₓ è T.
    • In caso contrario, se uno degli operatori in U converte in tipi inclusi in T, 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 in U. Se non è possibile trovare alcun tipo più incluso, la conversione è ambigua e si verifica un errore in fase di compilazione.
  • Trovare l'operatore di conversione più specifico:
    • Se U contiene esattamente un operatore di conversione definito dall'utente che esegue la conversione da Sₓ a Tₓ, questo è l'operatore di conversione più specifico.
    • In caso contrario, se U contiene esattamente un operatore di conversione lifted che converte da Sₓ a Tₓ, questo è l'operatore di conversione più specifico.
    • In caso contrario, la conversione è ambigua e si verifica un errore in fase di compilazione.
  • Infine, applicare la conversione:
    • Se E non ha già il tipo Sₓ, viene eseguita una conversione esplicita standard da E a Sₓ .
    • L'operatore di conversione definito dall'utente più specifico viene richiamato per eseguire la conversione da Sₓ a Tₓ.
    • Se Tₓ non Tè , viene eseguita una conversione esplicita standard da Tₓ a T .

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? a T?
  • Conversione implicita o esplicita da S a T?
  • Conversione esplicita da S? a T.

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? a T?:
    • Se il valore di origine è null (HasValue la proprietà è false), il risultato è il valore Null di tipo T?.
    • In caso contrario, la conversione viene valutata come un annullamento del wrapping da S? a S, seguita dalla conversione sottostante da a S, seguita da un wrapping da TT a T?.
  • Se la conversione nullable è da S a T?, la conversione viene valutata come conversione sottostante da S a T seguita da un wrapping da T a T?.
  • Se la conversione nullable è da S? a T, la conversione viene valutata come un annullamento del wrapping da S? a S seguito della conversione sottostante da S a T.

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 Tnon 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 TT 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 e F 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 di D output.
  • Se F ha un elenco di parametri tipizzato in modo esplicito, ogni parametro in D ha gli stessi modificatori del parametro corrispondente in F e esiste una conversione identity tra il parametro corrispondente in F.
  • 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 e D ha un tipo restituito void oF è asincrono e D ha un «TaskType» tipo restituito (§15.15.1), quando a ogni parametro viene F assegnato il tipo del parametro corrispondente in D, il corpo di F è un'espressione valida (w.r.t §12) che sarebbe consentita come statement_expression (§13.7).
  • Se il corpo di F è un blocco e Dha un tipo restituito void oF è asincrono e D ha un «TaskType» tipo restituito , quando a ogni parametro di F viene assegnato il tipo del parametro corrispondente in D, il corpo di F è un blocco valido (w.r.t §13.3) in cui nessuna return istruzione specifica un'espressione.
  • Se il corpo di F è un'espressione e F non è asincrono e D ha un tipo nonvoid restituito o«TaskType»<T>restituito (§15.15.1), quando a ogni parametro viene F assegnato il tipo del parametro corrispondente in , il corpo di D è un'espressione valida (w.r.t F) che è implicitamente convertibile in .T
  • Se il corpo di F è un blocco e F non è asincrono e D ha un tipo restituito non void oppureT«TaskType»<T>restituito, quando a ogni parametro viene F assegnato il tipo del parametro corrispondente in D, il corpo di è un blocco di F istruzioni valido (w.r.t §13.3) con un punto finale non raggiungibile in cui ogni istruzione return specifica un'espressione che è implicitamente convertibile Tin .

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 tipo AR:

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é, quando x viene specificato il tipo , int è un'espressione valida convertibile in modo implicito nel tipo x + 1int.

Analogamente, la seconda assegnazione converte correttamente la funzione anonima nel tipo delegato Func<int, double> perché il risultato di x + 1 (di tipo int) è convertibile in modo implicito nel tipo double.

Tuttavia, la terza assegnazione è un errore in fase di compilazione perché, quando x viene specificato il tipo double, il risultato di x + 1 (di tipo double) non è convertibile in modo implicito nel tipo int.

La quarta assegnazione converte correttamente la funzione asincrona anonima nel tipo Func<int, Task<int>> delegato perché il risultato di x + 1 (di tipo int) è convertibile in modo implicito nel tipo int restituito effettivo dell'espressione lambda asincrona, che ha un tipo Task<int>restituito .

esempio finale

Un'espressione F lambda è compatibile con un tipo di Expression<D> albero delle espressioni se F è compatibile con il tipo Ddelegato . 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 modulo E(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, outo ref) del parametro corrispondente nella parameter_list di , ad eccezione dei D parametri di tipo dynamic, dove l'espressione corrispondente ha il tipo object 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.
  • Una conversione viene considerata esistente se l'algoritmo di §12.8.10.2 produce un singolo metodo M migliore compatibile (§20.4) con D.
  • Se il metodo selezionato è un metodo M di istanza, l'espressione di istanza associata a E 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 gruppo F di metodi in un valore di tipo D1.

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 Edi 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'eccezione System.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.