(this == null) in C #!

129

Aufgrund eines Fehlers, der in C # 4 behoben wurde, wird das folgende Programm gedruckt true. (Probieren Sie es in 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()) { }
}

In VS2008 im Release-Modus wird eine InvalidProgramException ausgelöst. (Im Debug-Modus funktioniert es gut)

In VS2010 Beta 2 wird es nicht kompiliert (ich habe Beta 1 nicht ausprobiert). Das habe ich auf die harte Tour gelernt

Gibt es eine andere Möglichkeit, this == nullreines C # zu erstellen?

SLaks
quelle
3
Es ist höchstwahrscheinlich ein Fehler im C # 3.0-Compiler. Es funktioniert so, wie es in C # 4.0 sein sollte.
Mehrdad Afshari
82
@SLaks: Das Problem mit Fehlern ist, dass Sie erwarten können, dass sie irgendwann behoben werden. Daher ist es wahrscheinlich nicht ratsam, sie als "nützlich" zu betrachten.
AnthonyWJones
6
Vielen Dank! wusste nichts über LINQPad. es ist cool!
Dorn
8
Inwiefern ist das genau nützlich?
Allen Rice
6
Wie war dieser Fehler nützlich?
BlackTigerX

Antworten:

73

Diese Beobachtung wurde heute in einer anderen Frage auf StackOverflow veröffentlicht .

Marc ' gute Antwort auf diese Frage zeigt, dass Sie gemäß der Spezifikation (Abschnitt 7.5.7) thisin diesem Kontext nicht zugreifen können sollten und die Möglichkeit, dies im C # 3.0-Compiler zu tun, ein Fehler ist. Der C # 4.0-Compiler verhält sich gemäß der Spezifikation korrekt (selbst in Beta 1 ist dies ein Fehler bei der Kompilierung):

§ 7.5.7 Dieser Zugang

Ein dieser Zugriff besteht aus dem reservierten Wort this.

dieser Zugriff:

this

Ein Zugriff auf diesen Zugriff ist nur im Block eines Instanzkonstruktors, einer Instanzmethode oder eines Instanzzugriffs zulässig .

Mehrdad Afshari
quelle
2
Ich verstehe nicht, warum in dem in dieser Frage dargestellten Code die Verwendung des Schlüsselworts "this" ungültig ist. Das Verfahren ist eine normale CheckNull Instanzmethode, nicht - statische . Die Verwendung von "this" ist bei einer solchen Methode zu 100% gültig, und selbst ein Vergleich mit null ist gültig. Der Fehler befindet sich in der Basis-Init-Zeile: Es handelt sich um den Versuch, einen instanzgebundenen Delegaten als Parameter an den Basis-Ctor zu übergeben. Dies ist der Fehler (ein Loch in den sematischen Prüfungen) im Compiler: Es sollte NICHT möglich sein. Sie dürfen nicht schreiben, : base(CheckNull())wenn CheckNull nicht statisch ist, und Sie sollten auch nicht in der Lage sein, ein instanzgebundenes Lambda zu inline.
Quetzalcoatl
4
@quetzalcoatl: thisin CheckNullMethode ist legal. Was nicht legal ist, ist der implizite Zugriff auf diesen Zugriff im () => CheckNull()Wesentlichen () => this.CheckNull(), der außerhalb des Blocks eines Instanzkonstruktors ausgeführt wird. Ich bin damit einverstanden, dass sich der von mir zitierte Teil der Spezifikation hauptsächlich auf die syntaktische Rechtmäßigkeit von thisSchlüsselwörtern konzentriert, und wahrscheinlich befasst sich ein anderer Teil genauer mit diesem Problem, aber es ist auch einfach, konzeptionell aus diesem Teil der Spezifikation zu extrapolieren.
Mehrdad Afshari
2
Entschuldigung, ich bin anderer Meinung. Während ich das weiß (und das im obigen Kommentar geschrieben habe) und Sie das auch wissen, haben Sie die tatsächliche Ursache des Problems in Ihrer (akzeptierten) Antwort nicht erwähnt. Die Antwort wird akzeptiert - also hat der Autor sie anscheinend auch erfasst. Aber ich bezweifle, dass alle Leser so hell und fließend Lambdas beherrschen, um ein instanzgebundenes Lambda gegenüber einem statischen Lambda auf den ersten Blick zu erkennen und dies auf "dies" und Probleme mit emittiertem IL abzubilden :) Deshalb habe ich meine drei Cent hinzugefügt. Abgesehen davon stimme ich mit allem anderen überein, was von Ihnen und anderen gefunden, analysiert und beschrieben wurde :)
quetzalcoatl
24

Die Rohdekompilierung (Reflektor ohne Optimierungen) der Binärdatei im Debug-Modus lautet:

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

Die CompilerGenerated-Methode ist nicht sinnvoll. wenn man sich die IL (unten) aussehen, es ruft die Methode auf einem Null - Zeichenfolge (!).

   .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 

Im Freigabemodus wird die lokale Variable weg optimiert, sodass versucht wird, eine nicht vorhandene Variable auf den Stapel zu verschieben.

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

(Der Reflektor stürzt ab, wenn er in C # umgewandelt wird.)


EDIT : Weiß jemand (Eric Lippert?), Warum der Compiler das ausgibt ldloc?

SLaks
quelle
11

Ich habe das gehabt! (und bekam auch Beweise)

Alt-Text

Leppie
quelle
2
War spät dran, war ein Zeichen, dass ich aufhören sollte zu programmieren :) Hackte unser mit DLR-Zeug IIRC.
Leppie
Machen Sie einen Debugger-Visualizer (DebuggerDisplay) für alles, was "das" ist, und machen Sie sich zum Narren, der null ist? : D nur sagen
Ion Todirel
10

Dies ist kein "Bug". Dies ist, dass Sie das Typsystem missbrauchen. Sie sollten niemals einen Verweis auf die aktuelle Instanz ( this) an jemanden innerhalb eines Konstruktors übergeben.

Ich könnte einen ähnlichen "Fehler" erzeugen, indem ich auch eine virtuelle Methode innerhalb des Basisklassenkonstruktors aufrufe.

Nur weil man kann etwas Schlechtes tun nicht sein ein bedeuten Fehler , wenn Sie etwas von ihm zu bekommen.


quelle
14
Es ist ein Compiler-Fehler. Es wird eine ungültige IL generiert. (Lesen Sie meine Antwort)
SLaks
Der Kontext ist statisch, daher sollte Ihnen zu diesem Zeitpunkt keine Instanzmethodenreferenz gestattet werden.
Leppie
10
@ Will: Es ist ein Compiler-Fehler. Der Compiler soll gültigen , überprüfbaren Code für dieses Code-Snippet generieren oder eine Fehlermeldung ausspucken. Wenn sich ein Compiler nicht gemäß der Spezifikation verhält, ist er fehlerhaft .
Mehrdad Afshari
2
@ Will # 4: Als ich den Code schrieb, hatte ich nicht über die Auswirkungen nachgedacht. Ich habe erst festgestellt, dass es keinen Sinn macht, als es in VS2010 nicht mehr kompiliert wird. -
SLaks
3
Der virtuelle Methodenaufruf im Konstruktor ist übrigens eine vollständig gültige Operation. Es wird einfach nicht empfohlen. Es kann in logischen Katastrophen führen , aber nie ein InvalidProgramException.
Mehrdad Afshari
4

Ich könnte mich irren, aber ich bin mir ziemlich sicher, dass nulles niemals ein Szenario geben wird, in dem dies thiszutrifft.

Wie würden Sie zum Beispiel anrufen CheckNull?

Derived derived = null;
Console.WriteLine(derived.CheckNull()); // this should throw a NullReferenceException
Dan Tao
quelle
3
In einem Lambda im Konstruktorargument. Lesen Sie das gesamte Code-Snippet. (Und versuchen Sie es, wenn Sie mir nicht glauben)
SLaks
Ich stimme zu, obwohl ich mich nur schwach daran erinnere, dass in C ++ ein Objekt keine Referenz in seinem Konstruktor hatte, und ich frage mich, ob das Szenario (this == null) in diesen Fällen verwendet wird, um zu überprüfen, ob ein Aufruf einer Methode war erstellt aus dem Konstruktor des Objekts, bevor ein Zeiger auf "this" verfügbar gemacht wird. Soweit ich in C # weiß, sollte es jedoch keine Fälle geben, in denen "dies" jemals null sein würde, nicht einmal bei den Dispose- oder Finalisierungsmethoden.
Jpierson
Ich denke, mein Punkt ist, dass die Idee von thissich gegenseitig die Möglichkeit ausschließt, null zu sein - eine Art "Cogito, ergo sum" der Computerprogrammierung. Daher erscheint this == nullmir Ihr Wunsch, den Ausdruck zu verwenden und ihn jemals wahr werden zu lassen, falsch.
Dan Tao
Mit anderen Worten: Ich habe Ihren Code gelesen. Ich sage, ich frage mich, was Sie überhaupt erreichen wollten.
Dan Tao
Dieser Code demonstriert lediglich den Fehler und ist, wie Sie bereits betont haben, völlig nutzlos. Lesen Sie meine zweite Antwort, um wirklich nützlichen Code zu sehen.
SLaks
-1

Ich bin mir nicht sicher, ob Sie danach suchen

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

Beispiel: UserID = CheckForNull (Request.QueryString ["UserID"], 147);

Scott und das Entwicklerteam
quelle
13
Sie haben die Frage völlig falsch verstanden.
SLaks
1
Das habe ich mir gedacht. Ich dachte, ich würde es trotzdem versuchen.
Scott und das Entwicklerteam