(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.