2010-08-03 3 views
7

Ich habe den folgenden Code (Details zur Klarheit entfernt):Warum ist der "This" -Zeiger in einem Delegaten null?

private abstract class Base<TResult> { 
     private readonly System.Func<TResult> func = null; 

     protected Base(System.Func<TResult> func) { 
      this.func = func; 
     } 

     public TResult Execute() { 
      return this.func(); 
     } 
    } 

    private class Derived : Base<bool> { 
     public Derived(bool myValue) : base(delegate() { return this.MyValue(); }) { 
      this.myValue = myValue; 
     } 

     private bool myValue = false; 
     private bool MyValue() { 
      return this.myValue; // The "this" pointer is null here... 
     } 
    } 

    Derived d = new Derived(true); 
    bool result = d.Execute(); // This results in a null reference pointer (commented above) 

Irgendwelche Ideen?

Danke, Dave

+1

Warum definieren Sie eine abstrakte Klasse als privat? –

+0

@Mitch - es könnte verschachtelt werden, in diesem Fall völlig legal. –

+0

@Marc Gravell: sicher, aber eine verschachtelte private abstrakte Klasse klingt nicht sehr nützlich ... –

Antwort

6

Ist das überhaupt Rechts? this ist zu diesem Zeitpunkt nicht definiert. IIRC, das ist ein Compiler-Bug - bereits in 4.0 behoben.

Hier ist es in dem 4.0-Compiler:

Fehler 1 Schlüsselwort 'this' im aktuellen Kontext C nicht verfügbar ist: \ Benutzer \ Marc \ AppData \ Local \ Temporary Projects \ ConsoleApplication1 \ Program.cs 40 22 ConsoleApplication1

7.5.7 zu zitieren:

A this-Zugriff nur im Block eines Instanzkonstruktors erlaubt ist, eine Instanzmethode oder ein Instanzenaccessor. Es hat eine der folgenden Bedeutungen haben:

(emph Mine)

...

Verwendung dieser in einer Grundexpression in einem anderen Kontext als den oben aufgelisteten ist ein Fehler bei der Kompilierung Insbesondere ist es nicht möglich, in einer statischen Methode, einem statischen Eigenschaftsaccessor oder in einem Variableninitialisierer einer Felddeklaration darauf Bezug zu nehmen.

In dem angegebenen Beispiel ist es einfach ungültig.

+0

gerade in .NET 3.5 versucht und es kompiliert, aber Resharper beschwert sich ... –

+0

Das macht Sinn - war 3,5 ohne Resharper. – Dave

+0

Marc, könntest du mir meine Antwort ansehen: http://StackOverflow.com/Questions/3396782/Why-is-is-the-this-pointer-null-in-a-delegate/3397108#3397108 neben IL ist Müll ich kann nicht verstehen, warum 'call instance' verwendet wurde. – Andrey

5

Die Verwendung von this in einem Konstruktor ist immer gefährlich (außer im Spezialfall, in dem Sie einen Geschwisterkonstruktor aufrufen). Ihr Derived Konstruktor erfasst zum Zeitpunkt des Aufrufs this, was null ist, da die Instanz noch nicht konstruiert wurde.

+0

IIRC Das letzte Mal, als ich das sah, war der generierte IL Müll; es ist einfach ein Compiler-Bug - –

+0

@Marc, stimme ich zu; Ich habe deine Antwort neu gewählt. –

0

von an Ihrem Code suchen, würde ich sagen ist ein Design-Problem ...

Warum Sie nicht über die Execute Funktion abstrakt und lassen Sie die abgeleiteten Klassen bieten zu machen, was die Umsetzung sie wollen?

Zum Beispiel:

private abstract class Base<TResult> { 
    public abstract TResult Execute(); 
} 

private class Derived : Base<bool> { 
    //... 

    public override bool Execute(){ 
     return this.myValue; 
    } 

    //.... 
} 

Derived d = new Derived(true); 
bool result = d.Execute(); //This should work now 
+0

Eine gültige Alternative, über die ich nachgedacht habe, aber diese Route etwas generischer gewählt hat. Aufgrund der Rückmeldungen zum 4.0-Compiler sieht es jedoch so aus, als ob ich diesen Weg gehen müsste. Danke an alle für ihre Eingabe! Cheers, Dave – Dave

+0

@Dave: Ich bin mir nicht sicher, es wird generischer sein ... Was können Sie mit Delegierten erreichen, aber kann nicht mit Vererbung (in Ihrem Fall) erreicht werden? –

+0

Meine eigentliche Implementierung war etwas komplexer, und ich versuchte, meine Vererbungshierarchie durch die Verwendung von Funcs und Aktionen etwas flacher zu halten. Im Nachhinein denke ich, dass meine Lösung komplexer war als sie wert war. Ich schreibe es mit abstrakten Methoden oder vielleicht einer generischen Schnittstelle um. Danke für die Rückmeldung! – Dave

1

Es ist Compiler-Fehler und sehr seltsam. Lass mich Details erklären. Ich würde mich sehr freuen, wenn einige Experten das klären würden.

Ja, es ist falsch, this in ctor zu erfassen, aber die Situation wird heiß, weil this innerhalb anonymer Delegate verwendet wurde. Wenn der anonyme Delegat keine Schließung hat (äußere Variablen werden nicht erfasst), wird er normalerweise vom Compiler als statische Methode derselben Klasse implementiert. Es ist hier passiert.Aber lassen Sie uns einen Blick in den IL-Code werfen, der von dieser statischen Methode generiert wurde:

.method private hidebysig static bool <.ctor>b__0() cil managed 
{ 
    .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() 
    .maxstack 1 
    .locals init (
     [0] bool CS$1$0000) 
    L_0000: nop 
    L_0001: ldloc.0 
    L_0002: call instance bool ConsoleApplication15.Derived::MyValue() 
    L_0007: stloc.0 
    L_0008: br.s L_000a 
    L_000a: ldloc.0 
    L_000b: ret 
} 

Haben Sie das gesehen? Sehen Sie sich die Linie L_0002 und die Linie L_0001 genauer an. Es gibt zwei sehr seltsame Dinge:

  1. Wir haben versucht, Verfahren MyValue gegen bool zu nennen!
  2. Die Methode wurde als call aufgerufen, aber es ist nicht statisch, C# -Compiler ruft normalerweise Instanzmethoden mit callvirt auf! Wenn der callvirt verwendet wurde, würde dieser Aufruf fehlschlagen, weil callvirt auf this == null überprüft.

Jetzt lasst uns es brechen. Lassen Sie uns Schließung einführen und ändern Sie den Code zu:

public Derived(bool myValue) : base(delegate() { return myValue^this.MyValue(); }) { 
    this.myValue = myValue; 
} 

Und jetzt ist alles in Ordnung! Keine NRE! Anonyme Klasse wurde generiert und die Felder erfassen die Schließung. In diesem Fall wird IL korrekt generiert:

.method public hidebysig instance bool <.ctor>b__0() cil managed 
{ 
    .maxstack 2 
    .locals init (
     [0] bool CS$1$0000) 
    L_0000: nop 
    L_0001: ldarg.0 
    L_0002: ldfld bool ConsoleApplication15.Derived/<>c__DisplayClass1::myValue 
    L_0007: ldarg.0 
    L_0008: ldfld class ConsoleApplication15.Derived ConsoleApplication15.Derived/<>c__DisplayClass1::<>4__this 
    L_000d: call instance bool ConsoleApplication15.Derived::MyValue() 
    L_0012: xor 
    L_0013: stloc.0 
    L_0014: br.s L_0016 
    L_0016: ldloc.0 
    L_0017: ret 
} 

Methode gegen diese closured genannt wird. (aber immer noch mit call instance, hmm)

+0

Es ist ein Fehler; deshalb sind alle Wetten/Erwartungen der Vernunft aus - es ist keine Überanalyse wert ... –

Verwandte Themen