2010-09-23 9 views
23

Qualcosa mi è appena venuto in mente oggi che mi ha graffiato la testa.Com'è possibile il comportamento di boxe/unboxing di Nullable <T>?

Qualsiasi variabile di tipo Nullable<T> può essere assegnata a null. Per esempio:

int? i = null; 

All'inizio non potuto vedere come questo sarebbe possibile senza definire in qualche modo una conversione implicita da object a Nullable<T>:

public static implicit operator Nullable<T>(object box); 

Ma l'operatore sopra chiaramente non esiste, come se così fosse allora la seguente avrebbe anche dovuto essere legale, almeno al momento della compilazione (che non è):

int? i = new object(); 

poi ho capito che pe rhaps il tipo Nullable<T> potrebbe definire una conversione implicita a qualche tipo di riferimento arbitrario che non può mai essere istanziato, in questo modo:

public abstract class DummyBox 
{ 
    private DummyBox() 
    { } 
} 

public struct Nullable<T> where T : struct 
{ 
    public static implicit operator Nullable<T>(DummyBox box) 
    { 
     if (box == null) 
     { 
      return new Nullable<T>(); 
     } 

     // This should never be possible, as a DummyBox cannot be instantiated. 
     throw new InvalidCastException(); 
    } 
} 

Tuttavia, questo non spiega ciò che è accaduto a me la prossima: se la proprietà HasValue è false per qualsiasi Nullable<T> valore, allora tale valore verrà imballata come null:

int? i = new int?(); 
object x = i; // Now x is null. 

Inoltre, se HasValue è true, quindi il valore sono imballati come T piuttosto che un T?:

int? i = 5; 
object x = i; // Now x is a boxed int, NOT a boxed Nullable<int>. 

Ma questo sembra implicare che v'è una conversione implicita personalizzato da Nullable<T> a object:

public static implicit operator object(Nullable<T> value); 

questo non è chiaramente il caso come object è una classe base per tutti i tipi e le conversioni implicite definite dall'utente per/da tipi di base sono illegali (come dovrebbero essere).

Sembra che object x = i; dovrebbe scatola i come qualsiasi altro tipo di valore, in modo che x.GetType() si determina lo stesso risultato typeof(int?) (anziché gettare un NullReferenceException).

Così ho scavato un po 'intorno e, abbastanza sicuro, si scopre che questo comportamento è specifico per il tipo Nullable<T>, appositamente definito sia nel C# e le specifiche VB.NET, e non riproducibile in qualsiasi definita dall'utente struct (C#) o Structure (VB.NET).

Ecco perché sono ancora confuso.

Questo particolare comportamento di boxe e unboxing sembra impossibile da implementare a mano. Funziona solo perché sia ​​C# che VB.NET danno un trattamento speciale al tipo Nullable<T>.

  1. Non è teoricamente possibile che un linguaggio di base CLI diversa potrebbe esistere dove Nullable<T> non hanno avuto questo trattamento speciale? E il tipo Nullable<T> non presenterebbe quindi comportamento diverso in diverse lingue?

  2. In che modo C# e VB.NET raggiungono questo comportamento? È supportato dal CLR? (Cioè, fa il CLR permette un tipo in qualche modo "override" il modo in cui viene confezionato, anche se C# e VB.NET stessi lo vieta?)

  3. E 'anche possibile (in C# o VB.NET) per inscatolare un Nullable<T> come object?

+2

È il compilatore JIT che implementa il comportamento. Più qui: http://stackoverflow.com/questions/1583050/performance-surprise-with-as-and-nullable-types/3076525#3076525 –

+1

Mi rendo conto che sono in ritardo di 7 anni per la festa. Ma vorrei suggerire a chiunque sia curioso come me di leggere anche la fonte di riferimento per ulteriori approfondimenti. https://referencesource.microsoft.com/#mscorlib/system/nullable.cs, ffebe438fd9cbf0e – Licht

risposta

26

Ci sono due cose che accadono:

1) Il compilatore considera "nullo" non come un null di riferimento ma come un nulla valore ... il valore null per qualsiasi tipo di cui ha bisogno convertire in Nel caso di un Nullable<T> è solo il valore che ha False per il campo/proprietà HasValue. Pertanto, se si dispone di una variabile di tipo int?, è possibile che il valore di tale variabile sia null - è sufficiente cambiare la propria comprensione di ciò che null significa un po '.

2) I tipi Null di boxing ricevono un trattamento speciale da parte del CLR stesso. Questo è importante nel vostro secondo esempio:

int? i = new int?(); 
    object x = i; 

il compilatore sarà casella qualsiasi tipo di valore nullable in modo diverso per i valori di tipo non nullable. Se il valore non è nullo, il risultato sarà lo stesso del valore di boxing uguale a un valore di tipo non annullabile, quindi uno int? con valore 5 viene inserito nello stesso modo di uno int con valore 5 - il "nullability" è perduto. Tuttavia, il valore nullo di un tipo nullable è racchiuso solo nel riferimento null, piuttosto che nel creare un oggetto.

Questo è stato introdotto in ritardo nel ciclo CLR v2, su richiesta della comunità.

Significa che non esiste un valore di tipo "valore nullo a valore in scatola".

+2

Si accende come al solito - guardando il codice IL, l'oggetto x = mi viene convertito in un'istruzione che indica il supporto a livello CLR. – VinayC

+0

Vedo cosa intendi per il pugilato che riceve un trattamento speciale dal CLR: ho appena scritto un test rapido e ho esaminato l'IL in Reflector e ho notato che il compilatore C# non sembra fare nulla di speciale per rimuovere il boxing di 'Nullable 'stesso. Ma allora non è strano che l'effetto della boxe di un 'Nullable ' sia specificato indipendentemente nelle specifiche C# e VB.NET? È anche nelle specifiche CLI, lo sai? –

+0

@Dan: Sì, è nella specifica C#. Non so se è nelle specifiche VB.NET. Nella specifica CLI (ECMA-335) è nella sezione 8.2.4 della partizione 1. –

2

Hai capito bene: Nullable<T> ottiene un trattamento speciale da parte del compilatore, sia in VB e C#. Pertanto:

  1. Sì. Il compilatore di lingue deve utilizzare il caso speciale Nullable<T>.
  2. L'utilizzo del refill del compilatore di Nullable<T>. Gli operatori sono solo zucchero sintattico.
  3. Non che io sappia.
+0

Quindi in risposta alla tua seconda risposta: il compilatore C# (per esempio) deve rifattorizzare 'oggetto x = i;' in qualcosa come 'oggetto x = i.HasValue? (oggetto) i.Valore: null; 'ho ragione? Il che significa che la lingua in realtà non consente affatto il comportamento standard della boxe (mi sono appena reso conto che 'object x = (int?) 5;' rende 'x' un boxed 'int', * not *' Nullable '). Interessante ... –

+0

Guardando a IL, CLR ha un supporto speciale a Nullable. Il compilatore emette l'istruzione di casella e il valore di controllo CLR per decidere se impostare il riferimento al valore null o al valore del tipo di valore effettivo della casella. – VinayC

+0

Dall'osservazione di IL generato da un metodo 'GetRandomNullable ' Mi sono appena avviato in Reflector, sembra che VinayC abbia ragione: non vedo il compilatore C# che in realtà rifatta il pugilato, il che implica (per me) che il trattamento speciale effettivamente * fa * si verifica a livello CLR. Ma se questo è vero, allora sembra strano (per me) che il comportamento sia definito nelle specifiche linguistiche stesse. qualche idea? –