(this == null) w C #!

129

Z powodu błędu, który został naprawiony w C # 4, drukuje następujący program true. (Wypróbuj w LINQPad)

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 wydania zgłasza wyjątek InvalidProgramException. (W trybie debugowania działa dobrze)

W VS2010 Beta 2 nie kompiluje się (nie próbowałem Beta 1); Nauczyłem się tego na własnej skórze

Czy istnieje inny sposób tworzenia this == nullw czystym języku C #?

SLaks
źródło
3
Najprawdopodobniej jest to błąd w kompilatorze C # 3.0. Działa tak, jak powinien w C # 4.0.
Mehrdad Afshari
82
@SLaks: Problem z błędami polega na tym, że można oczekiwać, że w pewnym momencie zostaną one naprawione, więc znalezienie ich jako „użytecznych” prawdopodobnie nie jest mądre.
AnthonyWJones
6
dzięki! nie wiedziałem o LINQPad. to jest spoko!
thorn̈
8
W jakim dokładnie sensie jest to przydatne?
Allen Rice
6
jak ten błąd był przydatny?
BlackTigerX

Odpowiedzi:

73

Ta obserwacja została opublikowana w StackOverflow w innym pytaniu w dniu dzisiejszym.

Marc „s wielki odpowiedź na to pytanie wskazuje, że zgodnie z specyfikacją (rozdział 7.5.7), nie powinny być w stanie uzyskać dostęp thisw tym kontekście oraz możliwość to zrobić w C # 3.0 kompilator jest błąd. Kompilator C # 4.0 zachowuje się poprawnie zgodnie ze specyfikacją (nawet w wersji Beta 1 jest to błąd czasu kompilacji):

§ 7.5.7 Ten dostęp

Ten dostępu składa się z zastrzeżonego słowa this.

ten dostęp:

this

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

Mehrdad Afshari
źródło
2
Nie widzę, dlaczego w przedstawionym w tym pytaniu kodzie użycie słowa kluczowego „to” jest nieprawidłowe. Metoda CheckNull jest zwykłą metodą instancji, niestatyczną . Użycie "this" jest w 100% poprawne w takiej metodzie, a nawet porównanie tego do null jest poprawne. Błąd znajduje się w linii bazowej init: jest to próba przekazania delegata związanego z instancją jako parametru do podstawowego kontrolera. To jest błąd (dziura w sprawdzeniach sematycznych) w kompilatorze: NIE powinno to być możliwe. Nie możesz pisać, : base(CheckNull())jeśli CheckNull nie jest statyczny, i podobnie nie powinieneś być w stanie wstawić lambdy związanej z instancją.
quetzalcoatl
4
@quetzalcoatl: thisw CheckNullmetodzie jest legalne. Co nie jest zgodne z prawem jest niejawna tego dostępu w () => CheckNull()istocie () => this.CheckNull(), która działa poza blokiem z konstruktora instancji. Zgadzam się, że część specyfikacji, którą cytuję, koncentruje się głównie na składniowej legalności thissłowa kluczowego i prawdopodobnie inna część odnosi się do tej kwestii bardziej precyzyjnie, ale również z tej części specyfikacji łatwo jest koncepcyjnie ekstrapolować.
Mehrdad Afshari,
2
Przepraszam, nie zgadzam się. Chociaż ja to wiem (i napisałem to w komentarzu powyżej), a Ty też to wiesz - w swojej (zaakceptowanej) odpowiedzi nie wspomniałeś o rzeczywistej przyczynie problemu. Odpowiedź jest akceptowana - więc najwyraźniej autor też ją złapał. Ale wątpię, czy wszyscy czytelnicy będą tak błyskotliwi i biegli w lambdach, aby na pierwszy rzut oka rozpoznać lambdę związaną z instancją w porównaniu z lambdą statyczną i odwzorować to na 'this' i problemy z emitowanym IL :) Dlatego dodałem moje trzy centy. Poza tym zgadzam się ze wszystkim innym, co zostało znalezione, przeanalizowane i opisane przez Ciebie i innych :)
quetzalcoatl
24

Surowa dekompilacja (Reflector bez optymalizacji) pliku binarnego trybu debugowania to:

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

Metoda CompilerGenerated nie ma sensu; jeśli spojrzysz na IL (poniżej), wywołuje ona metodę na łańcuchu pustym (!).

   .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 wydania zmienna lokalna jest zoptymalizowana, więc próbuje umieścić nieistniejącą zmienną na stosie.

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

(Reflektor ulega awarii podczas przekształcania go w C #)


EDYCJA : Czy ktoś (Eric Lippert?) Wie, dlaczego kompilator emituje ldloc?

SLaks
źródło
11

Miałem to! (i mam też dowód)

tekst alternatywny

leppie
źródło
2
Spóźniłem się, był to znak, że powinienem przestać kodować :) Włamałem się do nas z DLR IIRC.
leppie
zrobić wizualizator debuggera (DebuggerDisplay) dla czegokolwiek „to” i sprawić, że to cię oszuka, że ​​jest zerowe? : D just
10

To nie jest „błąd”. To ty nadużywasz systemu typów. Nigdy nie należy przekazywać odwołania do bieżącej instancji ( this) nikomu w konstruktorze.

Mógłbym stworzyć podobny „błąd”, wywołując metodę wirtualną w konstruktorze klasy bazowej.

Tylko dlatego, że możesz zrobić coś złego, nie oznacza, że ​​jest to błąd, gdy zostaniesz przez to ugryziony.


źródło
14
Jest to błąd kompilatora. Generuje nieprawidłowy IL. (Przeczytaj moją odpowiedź)
SLaks
Kontekst jest statyczny, więc na tym etapie nie powinno być dozwolone odwołanie do metody wystąpienia.
leppie
10
@Will: To błąd kompilatora. Kompilator powinien wygenerować prawidłowy , weryfikowalny kod dla tego fragmentu kodu lub wypluć komunikat o błędzie. Gdy kompilator nie zachowuje się zgodnie ze specyfikacją, zawiera błędy .
Mehrdad Afshari
2
@ Will # 4: Kiedy pisałem kod, nie myślałem o konsekwencjach. Zdałem sobie sprawę, że nie ma to sensu, gdy przestał kompilować w VS2010. -
SLaks
3
Nawiasem mówiąc, wywołanie metody wirtualnej w konstruktorze jest całkowicie poprawną operacją. Po prostu nie jest to zalecane. Może to spowodować logiczne katastrofy, ale nigdy nie InvalidProgramException.
Mehrdad Afshari
3

Mogę się mylić, ale jestem prawie pewien, że jeśli twoim celem jest nullto, nigdy nie będzie scenariusza, który thismiałby zastosowanie.

Na przykład, jak byś zadzwonił CheckNull?

Derived derived = null;
Console.WriteLine(derived.CheckNull()); // this should throw a NullReferenceException
Dan Tao
źródło
3
W lambdzie w argumencie konstruktora. Przeczytaj cały fragment kodu. (I spróbuj, jeśli mi nie wierzysz)
SLaks
Zgadzam się, chociaż pamiętam słabo coś o tym, jak w C ++ obiekt nie miał odwołania w swoim konstruktorze i zastanawiam się, czy scenariusz (this == null) jest używany w tych przypadkach do sprawdzenia, czy wywołanie metody było wykonane z konstruktora obiektu przed wystawieniem wskaźnika na „this”. Chociaż, o ile wiem w C #, nie powinno być żadnych przypadków, w których „this” kiedykolwiek miałoby wartość null, nawet w metodach Dispose lub finalization.
jpierson
Myślę, że chodzi mi o to, że sama idea thiswyklucza się wzajemnie z możliwością bycia zerowym - coś w rodzaju „Cogito, ergo sum” programowania komputerowego. Dlatego twoje pragnienie, by użyć tego wyrażenia this == nulli kiedykolwiek przywrócić mu prawdę, wydaje mi się błędne.
Dan Tao
Innymi słowy: przeczytałem twój kod; mówię, że kwestionuję to, co próbowaliście osiągnąć w pierwszej kolejności.
Dan Tao
Ten kod po prostu demonstruje błąd i, jak zauważyłeś, jest całkowicie bezużyteczny. Aby zobaczyć naprawdę użyteczny kod, przeczytaj moją drugą odpowiedź.
SLaks
-1

Nie jestem pewien, czy tego właśnie 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 ["ID użytkownika"], 147);

Scott i zespół deweloperów
źródło
13
Jesteś całkowicie niezrozumiany pytanie.
SLaks
1
Doszedłem do tego. Pomyślałem, że i tak spróbuję.
Scott i zespół deweloperów