2009-08-12 6 views
9

Czy null ma typ? Jaka jest wartość pusta reprezentowana wewnętrznie? Co dzieje się w poniższym kodzie?W .Net/C#, czy null jest mocno wpisany?

void Foo(string bar) {...} 
void Foo(object bar) {...} 

Foo((string)null); 

Edycja: Odpowiedzi do tej pory były niespecyficzne i zbyt wysokiego poziomu. Rozumiem, że obiekt typu odwołania składa się ze wskaźnika na stosie, który wskazuje na położenie na stercie, które zawiera indeks bloku synchronizacji, uchwyt typu i pola obiektu. Kiedy ustawiam instancję obiektu na null, gdzie dokładnie wskazuje wskaźnik na stosie? A w fragmencie kodu, czy obsada jest po prostu używana przez kompilator C#, aby zdecydować, które przeciążenie wywołać, i nie ma żadnego rzucania null?

Szukam szczegółowej odpowiedzi od kogoś, kto rozumie elementy wewnętrzne CLR.

Odpowiedz

16

Obsada do string w kodzie przykładowym nie daje null typu, ponieważ null nie może mieć samego typu. Jeśli chcesz dowód tego, a następnie wykonać następujący kod, gdzie można zobaczyć, że null jest zawsze równe sobie, niezależnie od tego, jaki typ zmiennej został przydzielony, to:

string s = null; 
IPAddress i = null; 
Console.WriteLine(object.Equals(s, i)); // prints "True" 
Console.WriteLine(object.ReferenceEquals(s, i)); // prints "True" 

Co obsada robi to powiedzieć kompilator, który przeciąża do wyboru.Ponieważ null nie ma typu, nie wie, czy wybrać przeciążenie, które ma wartość object lub string, ponieważ wartość ta może być interpretowana jako jedna z tych wartości. Więc pomagasz, mówiąc "Oto pusta wartość, która powinna być traktowana tak, jakby była ciągiem".

Jeśli chcesz zobaczyć, co się dzieje pod spodem, spójrz na IL z kodu. Odpowiedni bit do wywołania metody jest coś jak następujące w tekstowym IL (w zależności od obszaru nazw i klasy nazwie, etc):

ldnull 
call void ConsoleApplication1.Program::Foo(string) 

Więc wszystko, co się dzieje jest to, że zerowa jest ładowany na stosie, a to jest zużywane przez przeciążenie, które pobiera ciąg, ponieważ w czasie kompilacji wykonuje się analizę przeciążenia, więc metoda wywołania jest wypalana w IL.

Jeśli chcesz zobaczyć, co ldnull robi i dlaczego to różni się od po prostu przy użyciu coś jak ldc.i4.0 załadować zera na stosie następnie zobaczyć this answer (jeśli nie chcesz śledzić link, powodem jest to, że zero zerowe, które inaczej nie występuje w CLR).

+0

Więc co dokładnie będzie na stosie, kiedy mam na myśli 'null'? –

+0

@Matt - Edytowany, aby dodać więcej szczegółów niskiego poziomu. Czy to wystarczy? –

+0

@Greg: Dzięki - Byłem zajęty w pracy, więc nie mają czasu, aby sprawdzić, IL oraz ECMA wg planu siebie. To podrapało moją intelektualną świąd. –

2

Będzie wywoływał Foo (string string).

Możesz rzucić null grzywnę.

+0

Oczywiście - proszę zobaczyć moje zmienił. –

1

Słowo kluczowe null jest literałem, który reprezentuje odniesienie zerowe, takie, które nie odnosi się do żadnego obiektu. Wartość null jest domyślną wartością zmiennych typu referencyjnego. Od MSDN. Tak więc domyślna wartość String w twoim przykładzie.

// A null string is not the same as an empty string. 
string s = null; 
string t = String.Empty; // Logically the same as "" 

Co robisz jest równoważne użyciu domyślnego jak powyżej:

int equal = string.Compare((string)null, default(string)); 
2

Po ustawieniu obiektu odniesienia do null, wskaźnik (tył) wskazuje na szczególne miejsce w pamięci jest oznaczone jako null. Tak więc, gdy rzutujesz na null, twoja rzeczywistość tworzy wskaźnik określonego typu, który wskazuje na null.

Jak wykazano przez innych (1)(2), to nie wydaje się być w przypadku, ponieważ kompilator rozwiązuje przeciążeniu emitujących IL według określonego null obsady.

Wiem, że zmienne referencyjne mają typ skojarzony z nimi. Myślałem, że to będzie za tym castingiem null, ale myliłem się.

Kod operacji wypychanej zeruje referencję zerową (typ O) na stos. I nawet jeśli to odwołanie zerowe ma powiązany z nim typ, to call akceptuje to odwołanie jako poprawny argument string. Przynajmniej to rozumiem. Jeśli ktoś ma jakieś poprawki w tym zakresie, prosimy o poprawienie mnie.

1

NULL jest znacznikiem, nie ma typu danych. Gdy przypiszesz .NULL do pola lub zmiennej, wartość zmieni się na NULL, ale typ danych pola lub zmiennej nie zmienia się.

Powód przypisywania typu łańcucha znaków do null w twoim przykładzie, jak sądzę, ma na celu pokazanie metody czarownicy (w tym przypadku "void Foo (string bar) {...}", ponieważ jest to metoda, która akceptuje ciągi jako argumenty.)

Jeśli chciałbyś nazwać Foo ((object) null); druga metoda byłaby użyta. ("void Foo (object bar) {...}")

6

null nie ma typu, a "A w kodzie kodu, obsada jest po prostu używana przez kompilator C# zdecydować, które przeciążenie wywołać, a tak naprawdę nie ma żadnego rzucania nieważności? " dokładnie to się dzieje. Spójrzmy na wygenerowaną IL.

IL_0001: ldnull 
    IL_0002: call  void ConsoleApplication1.Program::Foo(string) 

Zwróć uwagę na pustą wartość ładowania Ldnull na stosie. Jest to po prostu wartość null, nie zero jako ciąg lub coś innego. Liczy się druga linia, w której IL jawnie wywołuje przeciążenie, które odbiera ciąg znaków. Oto co się dzieje, jeśli zadzwonisz, casting do obiektu:

IL_0001: ldnull 
    IL_0002: call  void ConsoleApplication1.Program::Foo(object) 

Tak, tak, obsada jest tak artefakt C# kompilator wie, co przeciążać zadzwonić.

1

typu wartości nullECMA-334 jest określona w następujący sposób:

11.2.7 zerowa typu

zerowa dosłownym (§9.4.4.6) zwraca wartość zerowa, co jest używany do oznaczenia odniesienia nie wskazując na żaden obiekt lub tablicy lub brak wartości. Typ zerowy ma jedną wartość, , która jest wartością pustą. Stąd wyrażenie o typie zerowym może oceniać tylko wartość pustą. Nie ma możliwości jawnego napisania typu zerowego, a zatem nie ma możliwości, aby użyć go w zadeklarowanym typie.

Typ null jest dolnym typem hierarchii typów - przeciwieństwem obiektu; typ zerowy może być uważany za podtyp każdego typu zerowalnego, ponieważ wartość pustą może być używana wszędzie tam, gdzie występuje dowolne wyrażenie null.

ta nie tworzy słabość układu typu, to znaczy, że każda operacja którego odbiornik typu pustych mogą być puste, a operacja może nie w czasie pracy z wyjątkiem. W systemach typu silnego operacje dostarczane przez typ są gwarantowane, jeśli odbiornik jest tego typu (tzn. Nie otrzymasz wyjątku wskaźnika pustego, co naprawdę mówi, że typ zerowy nie implementuje żadnej metody, którą masz wywołany na nim, chociaż wartość pusta może być wynikiem wyrażenia dowolnego typu, którego myślałeś, że używasz).

W kodzie, metody są przeciążone i statyczne rodzaj ekspresji plonowanie argument jest używany do rozpoznawania przeciążenia. Ze względu na gipsie na ciąg wyrażenie ma typ string (null typ jest podtypem sznurka jest to aż tak bezpieczne odlewana zgodnie z systemem typu C#). W pierwszym przybliżeniu, kompilator wybiera najbardziej specyficzny przeciążenie widoczne, więc wybiera wersję ciąg Foo zamiast wersji obiektu.

Powiązane problemy