2010-09-23 11 views
23

Coś, co właśnie przyszło mi dzisiaj do głowy, sprawiło, że podrapałem się w głowę.Jak jest możliwe boksowanie/rozpakowywanie Nullable <T>?

Dowolną zmienną typu Nullable<T> można przypisać do null. Na przykład:

int? i = null; 

Na początku nie mogłem zobaczyć, jak byłoby to możliwe bez jakiś sposób definiowania niejawna konwersja z object do Nullable<T>:

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

Ale powyżej operator wyraźnie nie istnieje, jako jeśli tak, to następuje również muszą być zgodne z prawem, przynajmniej w czasie kompilacji (co nie jest):

int? i = new object(); 

wtedy zdałem sobie sprawę, że PE rhaps typ Nullable<T> mógł zdefiniować niejawna konwersja do jakiegoś dowolnego typu referencyjnego, że nigdy nie może być instancja, tak:

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(); 
    } 
} 

to jednak nie wyjaśnia, co przyszło mi do głowy dalej: jeżeli nieruchomość HasValue jest false dla każdego Nullable<T> wartość, to wartość ta zostanie zapakowane jako null:

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

Ponadto, jeśli HasValue jest true, następnie wartością zostaną zapakowane jako T zamiast T?:

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

Ale to zdaje się sugerować, że istnieje zwyczaj niejawna konwersja z Nullable<T> do object:

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

To oczywiście nie jest przypadek jako object jest klasą bazową dla wszystkich typów, a zdefiniowane przez użytkownika niejawne konwersje do/z typów bazowych są niedozwolone (tak jak powinny być).

Wydaje się, że należy object x = i; pole i jak każdy inny rodzaj wartości, tak że x.GetType() dałoby taki sam efekt jak typeof(int?) (zamiast rzut NullReferenceException).

Więc wykopał się trochę, a na pewno wystarczy, okazuje się to zachowanie jest charakterystyczne dla rodzaju Nullable<T>, specjalnie zdefiniowane zarówno w C# i specyfikacji VB.NET, i nie powtarzalny w dowolnym zdefiniowanym przez użytkownika struct (C#) lub Structure (VB.NET).

Oto dlaczego wciąż jestem zdezorientowany.

Ten szczególny rodzaj boksowania i rozpakowywania wydaje się niemożliwy do wykonania ręcznie. Działa tylko dlatego, że zarówno C#, jak i VB.NET zapewniają specjalne traktowanie typu Nullable<T>.

  1. nie jest to teoretycznie możliwe, że może istnieć inny język CLI oparte gdzie Nullable<T> nie dano tej specjalnego traktowania? A zatem czy typ Nullable<T> nie będzie miał innego zachowania niż w różnych językach?

  2. W jaki sposób C# i VB.NET osiągnąć to zachowanie? Czy jest obsługiwany przez CLR? (To znaczy, nie CLR pozwalają typ jakoś „obejścia” sposób, w jaki jest ona pudełkową, choć C# i VB.NET sami zakazać go?)

  3. Czy to w ogóle możliwe (w języku C# lub VB.NET), aby umieścić w pudełku Nullable<T> jako object?

+2

Jest to kompilator JIT, który implementuje zachowanie. Więcej tutaj: http://stackoverflow.com/questions/1583050/performance-surprise-with-as-and-nullable-types/3076525#3076525 –

+1

Zdaję sobie sprawę, że spóźniłem się o 7 lat na imprezę. Chciałbym jednak zasugerować, aby każdy, kto jest ciekawy, jak ja, również przeczytał źródło referencji, aby uzyskać więcej informacji. https://referencesource.microsoft.com/#mscorlib/system/nullable.cs, ffebe438fd9cbf0e – Licht

Odpowiedz

26

Są dwie rzeczy dzieje:

1) traktuje kompilatora „null” nie jako null odniesienia ale jako zerową wartość ... wartość null dla każdego typu to potrzebuje przekonwertować na. W przypadku Nullable<T> jest to tylko wartość, która ma wartość False dla pola/właściwości HasValue. Więc jeśli masz zmienną typu int?, całkiem możliwe, że wartość tej zmiennej jest null - wystarczy zmienić nieco to, co znaczy null.

2) Bokserskie typy zerowane otrzymują specjalne traktowanie przez sam CLR. Jest to istotne w drugim przykładzie:

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

kompilator box dowolną wartość typu zerowalne odmiennie do wartości typu non-pustych. Jeśli wartość nie jest pusta, wynik będzie taki sam, jak w przypadku boksowania taką samą wartością, jak wartość typu nie podlegającego zerowaniu - tak więc int? z wartością 5 zostanie zapakowane w ten sam sposób co int o wartości 5 - "zerowalność" zgubiony. Jednak wartość pusta typu zerowego jest umieszczana w ramce tylko do odwołania zerowego, a nie do tworzenia obiektu w ogóle.

Zostało to wprowadzone na późnym etapie cyklu CLR v2, na prośbę społeczności.

Oznacza to, że nie ma czegoś takiego jak "wartość typu pudełkowego wartości zerowej".

+2

Spot - jak zwykle - wyszukując kod IL, obiekt x = i zostaje przekonwertowany na instrukcje box wskazujące wsparcie na poziomie CLR. – VinayC

+0

Widzę, co masz na myśli mówiąc o boksie, otrzymując specjalne traktowanie od CLR: Właśnie napisałem szybki test i spojrzałem na IL w Reflectorze i zauważyłem, że kompilator C# nie wydaje się robić nic specjalnego, aby usunąć boks z 'Nullable 'sama. Ale czy nie jest dziwne, że efekt boksowania 'Nullable ' jest określony niezależnie w specyfikacjach C# i VB.NET? Czy jest to również w specyfikacji CLI, czy wiesz? –

+0

@Dan: Tak, jest w specyfikacji C#. Nie wiem, czy jest to w specyfikacji VB.NET. W specyfikacji CLI (ECMA-335) znajduje się w sekcji 8.2.4 partycji 1. –

2

Masz rację: Nullable<T> otrzymuje specjalne traktowanie z kompilatora, zarówno w VB i C#. Dlatego:

  1. Tak. Kompilator języka wymaga specjalnego przypadku: Nullable<T>.
  2. Wykorzystanie przeliczników kompilatora Nullable<T>. Operatorzy są po prostu cukrem syntaktycznym.
  3. Nie to, co wiem.
+0

Więc w odpowiedzi na twoją drugą odpowiedź: kompilator C# (na przykład) musi refactor 'object x = i;' na coś w stylu 'object x = i.HasValue? (obiekt) i.Value: null; "mam rację? Co oznacza, że ​​język faktycznie uniemożliwia standardowe zachowanie boksu (po prostu zdałem sobie sprawę, że 'object x = (int?) 5;' czyni 'x' boxed' int', * not * 'Nullable '). Interesujące ... –

+0

Patrząc na IL, CLR ma specjalne wsparcie dla Nullable. Complier wysyła instrukcję box i CLR sprawdza wartość, aby zdecydować, czy ustawić ref do wartości pustej, czy też wartość rzeczywistej wartości pola. – VinayC

+0

Spoglądając na IL wygenerowany z metody 'GetRandomNullable ' po prostu pobiłem się w Reflectorze, wygląda na to, że VinayC ma rację: Nie widzę, żeby kompilator C# faktycznie refaktoryzował boks, co implikuje (dla mnie), że specjalne traktowanie faktycznie * nie * występuje na poziomie CLR. Ale jeśli to prawda, to wydaje się dziwne (dla mnie), że zachowanie będzie zdefiniowane w samych specyfikacjach językowych. jakieś pomysły? –

Powiązane problemy