2010-08-03 12 views
7

Ho il seguente codice (dettagli rimosse per chiarezza):Perché il "questo" puntatore è nullo in un delegato?

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) 

Tutte le idee?

Grazie, Dave

+1

perché stai definendo una classe astratta come privata? –

+0

@ Mitch: potrebbe essere annidato, in tal caso perfettamente legale. –

+0

@Marc Gravell: certo, ma una classe astratta privata annidata non sembra molto utile ... –

risposta

6

è che anche legale? this non è definito in quel punto. IIRC, questo è un bug del compilatore - già risolto in 4.0.

Eccolo nel compilatore 4.0:

di errore 1 chiave di 'questo' non è disponibile nel contesto attuale C: \ Users \ Marc \ AppData \ Local \ Temporary Projects \ ConsoleApplication1 \ Program.cs 22 40 ConsoleApplication1

citare 7.5.7:

a questo accesso è consentito solo nel blocco di un'istanza costruttore, un metodo di istanza o un accessorio di istanza. Ha uno dei seguenti significati:

(emph mio)

...

L'utilizzo di questo in un primario-espressione in un contesto diverso da quelli sopra elencati è un errore in fase di compilazione. In particolare, non è possibile fare riferimento a questo in un metodo statico, un accettore di proprietà statiche o in un inizializzatore di variabile di una dichiarazione di campo.

Nell'esempio fornito, è semplicemente non valido.

+0

appena provato in .NET 3.5 e viene compilato, ma Resharper si lamenta ... –

+0

Questo ha senso - si stava usando 3.5 senza Resharper. – Dave

+0

Marc, puoi dare un'occhiata alla mia risposta: http://stackoverflow.com/questions/3396782/why-is-the-this-pointer-null-in-a-delegate/3397108#3397108 accanto a IL is garbage I non riesco a capire perché sia ​​stato usato 'call instance'. – Andrey

5

L'utilizzo di this in un costruttore è sempre pericoloso (tranne nel caso speciale in cui si sta invocando un costruttore di pari livello). Il costruttore Derived acquisisce this al momento della sua chiamata, che è nullo poiché l'istanza non è stata ancora costruita.

+0

IIRC l'ultima volta che l'ho visto, l'IL generato era spazzatura; è semplicemente un bug del compilatore - –

+0

@Marc, sono d'accordo; Ho votato la tua risposta. –

0

Guardando il codice, direi che è un problema di progettazione ...

Perché non rendere la funzione Execute astratto e lasciare le classi derivate forniscono qualunque applicazione che vogliono?

Ad esempio:

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

Una valida alternativa a cui avevo pensato, ma ho scelto questa strada come un po 'più generica. Tuttavia, in base al feedback sul compilatore 4.0, sembra che dovrò seguire questa strada. Grazie a tutti per il loro contributo! Cheers, Dave – Dave

+0

@Dave: Non sono sicuro che sarà più generico ... Cosa puoi ottenere con un delegato ma non puoi ottenere con l'ereditarietà (nel tuo caso)? –

+0

La mia effettiva implementazione era un po 'più complessa, e stavo cercando di mantenere la gerarchia dell'ereditarietà un po' più piatta attraverso l'uso di Func e Actions. Con il senno di poi, penso che la mia soluzione fosse più complessa di quanto valesse. Vado a riscriverlo con metodi astratti, o forse con un'interfaccia generica. Grazie per il feedback! – Dave

1

E 'compilatore bug e molto strano. Lasciami spiegare i dettagli. Sarei davvero felice se alcuni esperti lo chiarissero.

Sì, non è corretto catturare this in Ctor, ma la situazione si riscalda perché this è stato utilizzato all'interno di un delegato anonimo. Normalmente, se il delegato anonimo non ha chiusura (non acquisisce variabili esterne) viene implementato dal compilatore come metodo statico della stessa classe. È successo quiMa diamo un'occhiata al codice IL generato da quel metodo statico:

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

hai visto? dare un'occhiata più da vicino alla linea L_0002 e alla linea L_0001. Ci sono due cose estremamente strane:

  1. abbiamo provato a chiamare il metodo MyValue contro bool!
  2. Il metodo è stato chiamato come call ma non è statico, il compilatore C# di solito chiama i metodi di istanza con callvirt! Se è stata utilizzata la callvirt, questa chiamata non riuscirà, perché callvirt controlla this == null.

Ora interromperlo. Introduciamo la chiusura e sostituiamo il codice a:

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

E ora è tutto a posto! No NRE! La classe anonima è stata generata e i campi catturano la chiusura. In questo caso corretto IL viene generato:

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

metodo è chiamato contro closured questo. (ma ancora con call instance, hmm)

+0

È un bug; quindi tutte le scommesse/aspettative di sanità mentale sono off - non vale la pena di un'analisi eccessiva ... –

Problemi correlati