(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 #?


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.


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?


11

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

tekst alternatywny


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.


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

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


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
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.