2009-10-21 11 views
123

Z powodu błędu, który został rozwiązany w C# 4, następujący program wypisuje true. (Spróbuj go w LINQPad)(to == null) w języku C#!

void Main() { new Derived(); } 

class Base { 
    public Base(Func<string> valueMaker) { Console.WriteLine(valueMaker()); } 
} 
class Derived : Base { 
    string CheckNull() { return "Am I null? " + (this == null); } 
    public Derived() : base(() => CheckNull()) { } 
} 

w VS2008 w trybie Release, to rzuca InvalidProgramException. (W trybie debugowania działa bez zarzutu)

W VS2010 Beta 2, nie kompiluje (nie wypróbowałem wersji beta 1); Dowiedziałem się, że twardy sposób

Czy istnieje inny sposób, aby this == null w czystym C#?

+3

To najprawdopodobniej błąd w kompilatorze C# 3.0. Działa tak, jak powinien w C# 4.0. –

+0

Tak, to nie powinno w ogóle kompilować IMO. – leppie

+0

co za ...?! Kto wpada na takie pomysły? Mimo to interesujący błąd, dzięki za udostępnienie –

Odpowiedz

70

Ta uwaga została opublikowana dzisiaj na StackOverflow w another question.

Marc „s great answer to that question wskazuje, że zgodnie z specyfikacją (rozdział 7.5.7), nie powinny być w stanie uzyskać dostęp this w tym kontekście oraz możliwość to zrobić w C# 3.0 kompilator jest błąd. C# 4.0 kompilator zachowuje się poprawnie według specyfikacji (nawet w wersji beta 1, to jest błąd czasu kompilacji):

§ 7.5.7 Dostęp ten

ten dostępu składa się z zarezerwowane słowo this.

tego dostępu:

this 

tego dostępu jest dozwolona tylko w bloku z konstruktora przykład, metodę przykład, lub przykład accessor.

+2

Nie widzę, dlaczego w kodzie przedstawionym w tym pytaniu użycie słowa kluczowego "to" jest nieprawidłowe. Metoda CheckNull jest normalną metodą instancji, ** niestatyczną **. Używanie "this" jest w 100% poprawne w takiej metodzie, a nawet porównanie do wartości null jest poprawne. Błąd znajduje się w podstawowej linii inicjującej: jest to próba przekazania uczestnikowi ograniczonemu przez instancję jako parametrowi do bazowego ctor. To jest błąd (dziura w sprawdzeniach sematycznych) w kompilatorze: NIE powinno być możliwe. Nie możesz pisać ': base (CheckNull())' jeśli CheckNull nie jest statyczny i podobnie nie powinieneś być w stanie wstawiać lambdy z instancją. – quetzalcoatl

+4

@quetzalcoatl: 'this' w metodzie' CheckNull' jest legalne. To, co nie jest legalne, to ** niejawny ** * ten-dostęp * w '() => CheckNull()', zasadniczo '() => this.CheckNull()', który działa poza ** blok ** konstruktora instancji. Zgadzam się, że część specyfikacji, którą przytaczam, koncentruje się głównie na syntaktycznej legalności słowa kluczowego "this", a prawdopodobnie inna część odnosi się do tego problemu w sposób bardziej precyzyjny, ale łatwo jest również ekstrapolować koncepcyjnie z tej części specyfikacji. –

+1

Przepraszam, nie zgadzam się. Chociaż wiem o tym (i napisałem to w powyższym komentarzu) i wiesz też, że - nie podałeś faktycznej przyczyny problemu w swojej (zaakceptowanej) odpowiedzi. Odpowiedź zostaje zaakceptowana - tak pozornie autor również ją zignorował. Ale wątpię, czy wszyscy czytelnicy będą tak bystre i płynne w lambdach, aby rozpoznawać instancjonowaną lambdę kontra statyczną lambdę od pierwszego wejrzenia i mapować to na "to" i problemy z emitowaną IL :) Dlatego dodałem moje trzy centy. Poza tym zgadzam się ze wszystkim, co zostało znalezione, przeanalizowane i opisane przez Ciebie i innych :) – quetzalcoatl

4

Mogę się mylić, ale jestem pewien, że jeśli Twój obiekt jest null, nigdy nie będzie scenariusza, który będzie obowiązywał pod warunkiem, że jest on objęty this.

Na przykład, jak nazwać CheckNull?

Derived derived = null; 
Console.WriteLine(derived.CheckNull()); // this should throw a NullReferenceException 
+3

W lambda w argumencie konstruktora. Przeczytaj cały fragment kodu. (I spróbuj, jeśli mi nie wierzysz) – SLaks

+0

Zgadzam się, chociaż pamiętam słabo coś o tym, jak w C++ obiekt nie miał referencji w jego konstruktorze i zastanawiam się, czy ten (= = zerowy) scenariusz jest używane w tych przypadkach do sprawdzenia, czy wywołanie metody zostało wykonane z konstruktora obiektu przed wystawieniem wskaźnika na "to". Chociaż, o ile wiem w C#, nie powinno być żadnych przypadków, w których "to" byłoby kiedykolwiek nieważne, nawet w metodach Dispose lub finalization. – jpierson

+0

Wartość pusta jest przechwytywana we właściwym momencie. –

10

Miałem to! (I mam dowód zbyt)

alt text

+0

Jak to zrobiłeś? – SLaks

+2

Spóźnił się, był znak, że powinienem przestać kodować :) Hackował nasz z DLR stuff IIRC. – leppie

+0

utworzyć wizualizator debuggera (DebuggerDisplay) dla dowolnego "tego" i sprawić, by ten głupiec był pusty? : D właśnie mówi: –

23

Smoły dekompilacji (reflektorowe bez optymalizacji) trybu debug:

private class Derived : Program.Base 
{ 
    // Methods 
    public Derived() 
    { 
     base..ctor(new Func<string>(Program.Derived.<.ctor>b__0)); 
     return; 
    } 

    [CompilerGenerated] 
    private static string <.ctor>b__0() 
    { 
     string CS$1$0000; 
     CS$1$0000 = CS$1$0000.CheckNull(); 
    Label_0009: 
     return CS$1$0000; 
    } 

    private string CheckNull() 
    { 
     string CS$1$0000; 
     CS$1$0000 = "Am I null? " + ((bool) (this == null)); 
    Label_0017: 
     return CS$1$0000; 
    } 
} 

Sposób CompilerGenerated nie ma sensu; jeśli spojrzysz na IL (poniżej), to wywołanie metody na wartości string (!).

.locals init (
     [0] string CS$1$0000) 
    L_0000: ldloc.0 
    L_0001: call instance string CompilerBug.Program/Derived::CheckNull() 
    L_0006: stloc.0 
    L_0007: br.s L_0009 
    L_0009: ldloc.0 
    L_000a: ret 

W trybie zwolnienia zmienna lokalna jest zoptymalizowana, więc próbuje przenieść nieistniejącą zmienną na stos.

L_0000: ldloc.0 
    L_0001: call instance string CompilerBug.Program/Derived::CheckNull() 
    L_0006: ret 

(wywala Reflektor gdy przekształcając go w C#)


EDIT: Czy ktoś (? Eric Lippert) wie, dlaczego kompilator emituje ldloc?

10

To nie jest "błąd". To nadużywasz systemu typu. Nigdy nie należy przekazywać odniesienia do bieżącej instancji (this) do kogokolwiek w obrębie konstruktora.

Mogłem utworzyć podobny "błąd", wywołując metodę wirtualną w obrębie konstruktora klasy podstawowej.

Tylko dlatego, że może zrobić coś złego, nie oznacza jego błąd, gdy pojawi się trochę o niego.

+14

To jest błąd kompilatora. Generuje nieprawidłowy IL. (Przeczytaj moją odpowiedź) – SLaks

+0

Kontekst jest statyczny, więc nie powinno być dozwolone odwoływanie się do metody instancji na tym etapie. – leppie

+0

Robisz coś, czego nigdy nie powinieneś robić, a potem shiz przerwy. A to błąd kompilatora? Znowu, są rzeczy, które możesz zrobić, których nie powinieneś robić, które się zepsują, to nie znaczy, że to błąd, ponieważ kompilator nie mógł znieść twojego działania źle. – Will

-1

Nie wiem, czy to jest to, czego szukasz

public static T CheckForNull<T>(object primary, T Default) 
    { 
     try 
     { 
      if (primary != null && !(primary is DBNull)) 
       return (T)Convert.ChangeType(primary, typeof(T)); 
      else if (Default.GetType() == typeof(T)) 
       return Default; 
     } 
     catch (Exception e) 
     { 
      throw new Exception("C:CFN.1 - " + e.Message + "Unexpected object type of " + primary.GetType().ToString() + " instead of " + typeof(T).ToString()); 
     } 
     return default(T); 
    } 

przykład: UserID = CheckForNull (Request.QueryString [ "UserID"], 147);

+13

Całkowicie niezrozumiałe pytanie. – SLaks

+1

Tak wymyśliłem. Myślałem, że i tak spróbuję. –