2012-04-16 8 views
31

Ho riscontrato alcuni comportamenti interessanti nell'interazione tra Nullable e conversioni implicite. Ho trovato che fornire una conversione implicita per un tipo di riferimento da un tipo di valore consente al tipo Nullable di essere passato a una funzione che richiede il tipo di riferimento quando invece mi aspetto un errore di compilazione. Il seguente codice illustra questo:Qual è la giustificazione per questo comportamento Nullable <T> con operatori di conversione implicita

static void Main(string[] args) 
{ 
    PrintCatAge(new Cat(13)); 
    PrintCatAge(12); 
    int? cat = null; 
    PrintCatAge(cat); 
} 

private static void PrintCatAge(Cat cat) 
{ 
    if (cat == null) 
     System.Console.WriteLine("What cat?"); 
    else 
     System.Console.WriteLine("The cat's age is {0} years", cat.Age); 
} 

class Cat 
{ 
    public int Age { get; set; } 
    public Cat(int age) 
    { 
     Age = age; 
    } 

    public static implicit operator Cat(int i) 
    { 
     System.Console.WriteLine("Implicit conversion from " + i); 
     return new Cat(i); 
    } 
} 

uscita:

The cat's age is 13 years 
Implicit conversion from 12 
The cat's age is 12 years 
What cat? 

Se il codice di conversione viene rimosso dal Cat allora si ottiene gli errori attesi:

Error 3 The best overloaded method match for 'ConsoleApplication2.Program.PrintCatAge(ConsoleApplication2.Program.Cat)' has some invalid arguments

Error 4 Argument 1: cannot convert from 'int?' to 'ConsoleApplication2.Program.Cat

Se si apre il file eseguibile con ILSpy il codice che è stato generato è il seguente

int? num = null; 
Program.PrintCatAge(num.HasValue ? num.GetValueOrDefault() : null); 

In un esperimento simile ho rimosso la conversione e ha aggiunto un sovraccarico di PrintCatAge che prende un int (non nullable) per vedere se il compilatore eseguirebbe un'operazione simile, ma non lo fa.

Capisco cosa sta succedendo, ma non ne capisco la giustificazione. Questo comportamento è inaspettato per me e sembra strano. Non ho avuto successo nel trovare alcun riferimento a questo comportamento su MSDN nella documentazione per le conversioni o Nullable<T>.

La domanda che pongo allora è, è questo intenzionale e c'è una spiegazione del perché questo sta accadendo?

+1

Interessante! Un comportamento inaspettatamente simile è ((oggetto) (int?) Null) == null che è vero. Un int nullo può essere inserito in un riferimento null. – usr

+0

Lanciare un valore null in questo modo: '(int?) Null' viene ottimizzato. Tuttavia, (null as int?) Provoca la creazione effettiva di un int? variabile temporanea. Ad ogni modo, mi aspetterei 'null == null' in questi casi. – Thomas

+2

@usr: Questo è previsto; il CLR ha un codice speciale per il nulla. In realtà è impossibile ottenere un 'Nullable ' in scatola. – SLaks

risposta

27

Ho detto prima che (1) questo è un bug del compilatore e (2) è uno nuovo. La prima affermazione è stata accurata; il secondo era che mi confondevo nella fretta di arrivare in tempo al bus. (Il bug che stavo pensando a questo è nuovo per me è un bug molto più complicato che coinvolge le conversioni e gli operatori incrementati.)

Questo è un bug del compilatore noto di lunga data. Jon Skeet l'ha portato alla mia attenzione qualche tempo fa e credo che ci sia una domanda StackOverflow su di esso da qualche parte; Non ricordo da dove sono arrivato. Forse Jon lo fa.

Quindi, il bug. Definiamo un operatore "sollevato". Se un operatore converte da un valore non nullable di tipo S a un valore non nullable di tipo T, allora vi è anche un operatore "sollevato" che converte da S? a T ?, tale che una S nulla? converte in una T nulla? e una S non nulla? si converte in T? svuotando S? a S, conversione da S a T e avvolgimento da T a T ?.

La specifica dice che (1) il unico situazione in cui v'è un operatore sollevato è quando S e T sono entrambi tipi di valore non annullabili, e (2) che gli operatori di conversione sollevata e non sollevati sono entrambi considerati se sono candidati applicabili per la conversione e se entrambi applicabili, quindi i tipi di origine e di destinazione delle conversioni applicabili, revocati o non revocati, vengono utilizzati per determinare il miglior tipo di fonte, il tipo di target migliore e, infine, migliore conversione di tutte le conversioni applicabili.

Sfortunatamente, l'implementazione viola completamente tutte queste regole, e lo fa in un modo che non possiamo cambiare senza rompere molti programmi esistenti.

Prima di tutto, violiamo la regola sull'esistenza di operatori sollevati. Un operatore sollevato è considerato dall'implementazione esistente se S e T sono entrambi tipi di valori non annullabili, o se S è un tipo di valore non annullabile e T è qualsiasi tipo a cui può essere assegnato un valore nullo: tipo di riferimento, valore nullable tipo, o tipo di puntatore. In tutti questi casi produciamo un operatore sollevato.

Nel vostro caso particolare, eleviamo a nullable dicendo che convertiamo un tipo nullable nel tipo di riferimento Cat verificando null. Se la fonte non è nulla, convertiamo normalmente; se lo è, allora produciamo un gatto nullo.

In secondo luogo, violiamo a fondo la regola su come determinare i migliori tipi di fonti e target di candidati applicabili quando uno di questi candidati è un operatore sollevato e violiamo anche le regole sulla determinazione di quale sia l'operatore migliore.

In breve, è un gran caos che non può essere risolto senza rompere i clienti reali, e quindi probabilmente incoraggeremo il comportamento in Roslyn. Prenderò in considerazione di documentare l'esatto comportamento del compilatore nel mio blog ad un certo punto, ma non terrei il fiato mentre aspetto quel giorno se fossi in te.

E, naturalmente, molte scuse per gli errori.

+1

Pensavo che solo Jon Skeet avesse risposto alle domande durante il pendolarismo. – BoltClock

+0

La grande differenza è che Jon può continuare a postare mentre si fa il pendolare. Tutte quelle videocamere hanno anche hotspot Wi-Fi? Non ne ho idea, davvero. –

+0

Ottimo! Beh, non il fatto che ci sia un bug, ma almeno non devo cercare di capire perché questo comportamento ha un senso :) – Thomas

Problemi correlati