(esto == nulo) en C #!

129

Debido a un error que se corrigió en C # 4, se imprime el siguiente programa true. (Pruébelo en 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()) { }
}

En VS2008 en modo Release, arroja una InvalidProgramException. (En modo de depuración, funciona bien)

En VS2010 Beta 2, no se compila (no probé Beta 1); Aprendí que de la manera difícil

¿Hay alguna otra forma de hacer this == nullen C # puro?

SLaks
fuente
3
Es muy probable que sea un error en el compilador C # 3.0. Funciona como debería en C # 4.0.
Mehrdad Afshari
82
@SLaks: El problema con los errores es que puede esperar que se solucionen en algún momento, por lo que encontrarlos "útiles" probablemente no sea sabio.
AnthonyWJones
66
¡Gracias! no sabía sobre LINQPad. ¡Es genial!
thorn̈
8
¿De qué manera, exactamente, es esto útil?
Allen Rice
66
¿Cómo fue útil este error?
BlackTigerX

Respuestas:

73

Esta observación ha sido publicada en StackOverflow en otra pregunta el día de hoy.

La gran respuesta de Marc a esa pregunta indica que de acuerdo con las especificaciones (sección 7.5.7), no debería poder acceder thisen ese contexto y la capacidad de hacerlo en el compilador C # 3.0 es un error. El compilador C # 4.0 se comporta correctamente de acuerdo con las especificaciones (incluso en Beta 1, este es un error de tiempo de compilación):

§ 7.5.7 Este acceso

Un acceso de este consiste en la palabra reservada this.

este acceso:

this

Se permite este acceso solo en el bloque de un constructor de instancias, un método de instancia o un descriptor de acceso de instancia.

Mehrdad Afshari
fuente
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 "esto" es 100% válido en dicho método, e incluso comparar esto con nulo es válido. El error está en la línea de inicio base: es el intento de pasar un delegado limitado por instancia como parámetro al ctor base. Este es el error (un agujero en las comprobaciones semánticas) en el compilador: NO debería ser posible. No tiene permiso para escribir : base(CheckNull())si CheckNull no es estático, y tampoco debería poder incorporar una lambda vinculada a la instancia.
quetzalcoatl
44
@quetzalcoatl: thisen el CheckNullmétodo es legal. Lo que no es legal es la implícita este acceso en () => CheckNull(), esencialmente () => this.CheckNull(), que se ejecuta fuera del bloque de un constructor de instancias. Estoy de acuerdo en que la parte de la especificación que cito se centra principalmente en la legalidad sintáctica de la thispalabra clave, y probablemente otra parte aborda este problema con mayor precisión, pero también es fácil extrapolar conceptualmente de esta parte de la especificación.
Mehrdad Afshari
2
Lo siento, estoy de acuerdo. Si bien lo sé (y lo escribí en el comentario anterior) y también lo sabes, no has mencionado la causa real del problema en tu respuesta (aceptada). La respuesta es aceptada, por lo que aparentemente el autor también la tomó. Pero dudo que todos los lectores sean tan brillantes y fluidos en lambdas para reconocer una instancia lambda-enlazada versus estática-lambda 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
24

La descompilación sin procesar (Reflector sin optimizaciones) del binario del modo 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 observa 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 el modo Release, la variable local se optimiza, por lo que intenta insertar una variable no existente en la pila.

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

(El reflector se bloquea al convertirlo en C #)


EDITAR : ¿Alguien (Eric Lippert?) Sabe por qué el compilador emite el ldloc?

SLaks
fuente
11

He tenido eso! (y obtuve pruebas también)

texto alternativo

leppie
fuente
2
Llegaba tarde, era una señal de que debía dejar de codificar :) Estaba pirateando nuestras cosas con DLR IIRC.
leppie
hacer un visualizador de depurador (DebuggerDisplay) para lo que sea que sea 'esto', y hacer que te engañe que es nulo? : D acaba de decir
10

Esto no es un "error". Estás abusando del sistema de tipos. Se supone que nunca debe pasar una referencia a la instancia actual ( this) a nadie dentro de un constructor.

También podría crear un "error" similar llamando a un método virtual dentro del constructor de la clase base.

El hecho de que pueda hacer algo malo no significa que sea un error cuando lo muerde.


fuente
14
Es un error del compilador. Genera IL no válida. (Lea mi respuesta)
SLaks
El contexto es estático, por lo que no se le debe permitir una referencia de método de instancia en esa etapa.
leppie
10
@ Will: es un error del compilador. Se supone que el compilador generará un código válido y verificable para ese fragmento de código o escupirá un mensaje de error. Cuando un compilador no se comporta de acuerdo con las especificaciones, tiene errores .
Mehrdad Afshari
2
@ Will # 4: Cuando escribí el código, no había pensado en las implicaciones. Solo me di cuenta de que no tenía sentido cuando dejó de compilar en VS2010. -
SLaks
3
Por cierto, la llamada al método virtual en el constructor es una operación completamente válida. Simplemente no es recomendable. Se puede dar lugar a desastres lógicas pero nunca una InvalidProgramException.
Mehrdad Afshari
3

Podría estar equivocado, pero estoy bastante seguro de que si su objeto es null, nunca habrá un escenario donde se thisaplique.

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

Derived derived = null;
Console.WriteLine(derived.CheckNull()); // this should throw a NullReferenceException
Dan Tao
fuente
3
En una lambda en el argumento constructor. Lea el fragmento de código completo. (Y probarlo si no me creen)
SLaks
Estoy de acuerdo, aunque recuerdo levemente algo acerca de cómo en C ++ un objeto no tenía referencia dentro de su constructor y me pregunto si el escenario (esto == nulo) se usa en esos casos para verificar si una llamada a un método era hecho desde el constructor del objeto antes de exponer un puntero a "esto". Sin embargo, hasta donde sé en C #, no debería haber ningún caso en el que "esto" alguna vez sea nulo, ni siquiera en los métodos Dispose o finalization.
jpierson
Creo que mi punto es que la idea misma thises mutuamente excluyente de la posibilidad de ser nula, una especie de "Cogito, ergo sum" de la programación de computadoras. Por lo tanto, su deseo de usar la expresión this == nully hacer que vuelva a ser verdadera me parece equivocada.
Dan Tao
En otras palabras: leí tu código; Lo que digo es que cuestiono lo que intentabas lograr en primer lugar.
Dan Tao
Este código simplemente demuestra el error y, como usted señaló, es completamente inútil. Para ver un código útil real, lea mi segunda respuesta.
SLaks
-1

No estoy seguro si esto es lo que estás buscando

    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: UserID = CheckForNull (Request.QueryString ["UserID"], 147);

Scott y el equipo de desarrollo
fuente
13
Usted completamente entendido mal la pregunta.
SLaks
1
Me imaginé tanto. Pensé que lo intentaría de todos modos.
Scott y el equipo de desarrollo