2010-08-03 19 views
7

tengo el siguiente código (detalles eliminadas para mayor claridad):¿Por qué el puntero "this" es nulo en un delegado?

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) 

¿Alguna idea?

Gracias, Dave

+1

¿por qué está definiendo una clase abstracta como privada? –

+0

@Mitch: podría estar anidado, en cuyo caso sería perfectamente legal. –

+0

@Marc Gravell: claro, pero una clase abstracta privada anidada no suena muy útil ... –

Respuesta

6

es que incluso legal? this no está definido en ese punto. IIRC, este es un error del compilador, ya corregido en 4.0.

Aquí está en el compilador 4.0:

de error 1 palabra clave 'esto' no está disponible en el contexto actual C: \ Users \ Marc \ AppData \ Projects temporales \ Local \ ConsoleApplication1 \ Program.cs 22 40 ConsoleApplication1

Para citar 7.5.7:

a este-acceso sólo está permitido en los bloques de un constructor de instancias, un método de instancia o un descriptor de acceso de instancia. Tiene uno de los siguientes significados:

(mina de emph)

...

El uso de este en una primaria-expresión en un contexto distinto de los mencionados anteriormente es una error en tiempo de compilación En particular, no es posible hacer referencia a esto en un método estático, un acceso de propiedad estático o en un inicializador de variable de una declaración de campo.

En el ejemplo dado, simplemente no es válido.

+0

acaba de probar en .NET 3.5 y compila, pero Resharper se queja ... –

+0

Eso tiene sentido - estaba usando 3.5 sin Resharper. – Dave

+0

Marc, podría echar un vistazo a mi respuesta: http://stackoverflow.com/questions/3396782/why-is-the-this-pointer-null-in-a-delegate/3397108#3397108 al lado de IL es basura I No puedo entender por qué se utilizó 'call instance'. – Andrey

5

Usar this en un constructor siempre es peligroso (excepto en el caso especial en el que está invocando un constructor hermano). Su constructor Derived captura this en el momento de su invocación, que es nulo ya que la instancia aún no se ha construido.

+0

IIRC La última vez que vi esto, el IL generado fue basura; es simplemente un error del compilador - –

+0

@Marc, estoy de acuerdo; Elegí tu respuesta. –

0

Al mirar a su código que diría es un problema de diseño ...

¿Por qué no hacer que la función abstracta Execute y dejar que las clases derivadas proporcionar cualquier aplicación que quieren?

Por ejemplo:

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 alternativa válida que había pensado, pero elegí esta ruta como un poco más genérica. Sin embargo, según los comentarios sobre el compilador 4.0, parece que tendré que seguir esa ruta. ¡Gracias a todos por su aporte! Cheers, Dave – Dave

+0

@Dave: No estoy seguro de que sea más genérico ... ¿Qué se puede lograr con un delegado pero no se puede lograr con la herencia (en su caso)? –

+0

Mi implementación real era un poco más compleja, y estaba tratando de mantener mi jerarquía de herencia un poco más plana mediante el uso de Funcs y Actions. Sin embargo, en retrospectiva, creo que mi solución fue más compleja de lo que valía. Me voy a reescribir con métodos abstractos, o quizás una interfaz genérica. ¡Gracias por la respuesta! – Dave

1

Es error del compilador y muy raro. Déjame explicarte los detalles. Estaría muy feliz si algunos expertos lo aclaran.

Sí, es incorrecto capturar this en ctor, pero la situación se calienta porque se utilizó this dentro de un delegado anónimo. Normalmente, si el delegado anónimo no tiene cierre (no captura las variables externas), el compilador lo implementa como método estático de la misma clase. Sucedió aquí.Pero echemos un vistazo al código IL generado de ese método estático:

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

¿lo viste? observe más de cerca la línea L_0002 y la línea L_0001. Hay dos cosas muy extrañas:

  1. tratamos de llamar al método MyValue contra bool!
  2. El método se llamó como call pero no es estático, el compilador de C# normalmente llama a los métodos de instancia con callvirt! Si se usó el callvirt, esta llamada fallaría, porque callvirt comprueba this == null.

Ahora vamos a romperlo. Vamos a presentar el cierre y cambiar el código a:

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

¡Y ahora todo está bien! ¡No, NRE! Se generó una clase anónima y sus campos capturan el cierre. En este caso se genera correcta IL:

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

método se llama contra clausurado esto. (pero aún con call instance, hmm)

+0

Es un error; por lo tanto, todas las apuestas/expectativas de cordura están apagadas; no vale la pena realizar un análisis excesivo ... –

Cuestiones relacionadas