2009-10-21 11 views
123

Debido a un error que se solucionó en C# 4, el siguiente programa imprime true. (Inténtelo en LINQPad)(esto == nulo) en C#!

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()) { } 
} 

En VS2008 en modo de lanzamiento, lanza una InvalidProgramException. (En el modo de depuración, funciona bien)

En VS2010 Beta 2, no compila (no probé Beta 1); Me enteré de la manera difícil

¿Hay alguna otra manera de hacer this == null en C# puro?

+3

Es muy probable que sea un error en el compilador C# 3.0. Funciona de la manera que debería en C# 4.0. –

+0

Sí, eso no debería compilar en absoluto IMO. – leppie

+0

¿qué ...?! ¿A quién se le ocurren esas ideas? Sin embargo, un error interesante, gracias por compartir –

Respuesta

70

Esta observación se ha publicado en StackOverflow en another question hoy.

Marc 's great answer to that question indica que de acuerdo con la especificación (sección 7.5.7), que no debería ser capaz de acceder this en ese contexto y la capacidad para hacerlo en C# 3.0 compilador es un error. C# 4.0 compilador está comportando correctamente de acuerdo con la especificación (incluso en la versión Beta 1, se trata de un error de tiempo de compilación):

§ 7.5.7 Este acceso

Un este acceso consiste en la palabra reservada this.

este acceso:

this 

A este acceso sólo se permite en el bloque de un constructor ejemplo, un método de instancia, o un descriptor de acceso instancia.

+2

No veo, por qué en el código presentado en esta pregunta, el uso de la palabra clave "this" no es válido. El método CheckNull es un método de instancia normal, ** no estático **. Usar "this" es 100% válido en dicho método, e incluso comparar esto con null es válido. El error está en la línea base init: es el intento de pasar delegado delimitado por instancia como un parámetro al ctor base. Este es el error (un agujero en las verificaciones sematic) en el compilador: NO debería ser posible. No está permitido escribir ': base (CheckNull())' si CheckNull no es estático, y no debe poder enlinear una lambda enlazada a instancia. – quetzalcoatl

+4

@quetzalcoatl: 'this' en el método' CheckNull' es legal. Lo que no es legal es el ** implícito ** * this-access * en '() => CheckNull()', esencialmente '() => this.CheckNull()', que se ejecuta fuera del ** bloque ** de un constructor de instancia. Estoy de acuerdo en que la parte de la especificación que cito se centra principalmente en la legalidad sintáctica de la palabra clave 'this', y probablemente otra parte aborde este tema con más precisión, pero también es fácil extrapolar conceptualmente esta parte de la especificación. –

+1

Lo siento, no estoy de acuerdo. Aunque sé eso (y lo he escrito en el comentario anterior) y también lo sabe, no mencionó la causa real del problema en su respuesta (aceptada). La respuesta es aceptada, por lo que aparentemente el autor también la grabó. Pero dudo que todos los lectores sean tan brillantes y fluidos en lambdas para reconocer una lambda instanciada frente a lambda estática a primera vista y mapear eso a 'esto' y problemas con la IL emitida :) Es por eso que agregué mis tres centavos. Aparte de eso, estoy de acuerdo con todo lo demás que fue encontrado, analizado y descrito por usted y otros :) – quetzalcoatl

4

Podría estar equivocado, pero estoy bastante seguro de que si su objeto es null, nunca habrá una situación en la que se aplique this.

Por ejemplo, ¿cómo llamarías a CheckNull?

Derived derived = null; 
Console.WriteLine(derived.CheckNull()); // this should throw a NullReferenceException 
+3

En una lambda en el argumento constructor. Lea todo el fragmento de código. (Y pruébalo si no me crees) – SLaks

+0

Estoy de acuerdo, aunque recuerdo débilmente algo sobre cómo en C++ un objeto no tenía referencia dentro de su constructor y me pregunto si el escenario (este == nulo) es utilizado en esos casos para comprobar si se realizó una llamada a un método desde el constructor del objeto antes de exponer un puntero a "this". Sin embargo, hasta donde sé en C#, no debería haber ningún caso en el que "esto" sea nulo alguna vez, ni siquiera en los métodos de eliminación o finalización. – jpierson

+0

El valor nulo se captura en el momento correcto. –

10

¡Lo he tenido! (Y consiguió una prueba demasiado)

alt text

+0

¿Cómo lo hiciste? – SLaks

+2

Era tarde, era una señal de que debería dejar de codificar :) Estaba pirateando nuestro material DLR IIRC. – leppie

+0

hacer un visualizador de depuración (DebuggerDisplay) para lo que sea 'esto', y hacer que te engañe que es nulo? : D solo diciendo ' –

23

La descompilación prima (Reflector con no optimizar) del modo binario de depuración es:

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

El método CompilerGenerated no tiene sentido; si nos fijamos en el IL (a continuación), está llamando al método en una cadena nula (!).

.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 

en modo de lanzamiento, la variable local está optimizado de distancia, por lo que trata de empujar una variable no existente en la pila.

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

(se bloquea cuando Reflector convirtiéndola en C#)


EDIT: ¿Hay alguien (? Eric Lippert) sabemos por qué el compilador emite la ldloc?

10

Esto no es un "error". Esto es abusar del sistema de tipos. Nunca se supone que debes pasar una referencia a la instancia actual (this) a cualquier persona dentro de un constructor.

Podría crear un "error" similar al invocar un método virtual dentro del constructor de la clase base también.

El hecho de que puede hacer algo malo, no significa que es un error cuando llegue poco a ella.

+14

Es un error del compilador. Genera IL no válida. (Lea mi respuesta) – SLaks

+0

El contexto es estático, por lo que no se le debe permitir una referencia de método de instancia en esa etapa. – leppie

+0

Hace algo que nunca debe hacer, luego se rompe. ¿Y ese es un error de compilación? Una vez más, hay cosas que puedes hacer y que no se supone que debes hacer, que se romperán, pero eso no significa que sea un error porque el compilador no podría manejar tu actuación mal. – Will

-1

No estoy seguro si esto es lo que busca

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

ejemplo: ID de usuario = CheckForNull (Request.QueryString [ "ID de usuario"], 147);

+13

Usted _completely_ malentendió la pregunta. – SLaks

+1

Pensé mucho. Pensé que lo intentaría de todos modos. –