2012-05-16 10 views
33

Siempre he pensado que es imposible que this sea nulo dentro del cuerpo del método de instancia. Seguir un programa simple demuestra que es posible. ¿Es esto un comportamiento documentado?this == null método de instancia .NET - ¿por qué es posible?

class Foo 
{ 
    public void Bar() 
    { 
     Debug.Assert(this == null); 
    } 
} 

public static void Test() 
{    
    var action = (Action)Delegate.CreateDelegate(typeof (Action), null, typeof(Foo).GetMethod("Bar")); 
    action(); 
} 

ACTUALIZACIÓN

Estoy de acuerdo con las respuestas diciendo que es como se documenta este método. Sin embargo, realmente no entiendo este comportamiento. Especialmente porque no es como se diseña C#.

que habíamos conseguido un informe de alguien (probablemente uno de los grupos .NET usando C# (pensaban que aún no se denomina C# en ese momento)) que tenía código escrito que llama a un método en un nulo puntero, pero no lo hicieron obtener una excepción porque el método no accedió a ningún campo (es decir, "this" era nulo, pero no había nada en el método utilizado). Ese método luego llamó a otro método que usó el punto y arrojó una excepción , y se produjo un poco de arañazo en la cabeza. Después de que lo calcularon , nos enviaron una nota al respecto. Pensamos que poder llamar a un método en una instancia nula era un poco extraño. Peter Golde hizo algunas pruebas para ver cuál era el impacto en el rendimiento de usar siempre callvirt, y fue lo suficientemente pequeño como para decidir para realizar el cambio.

http://blogs.msdn.com/b/ericgu/archive/2008/07/02/why-does-c-always-use-callvirt.aspx

+0

Consulte mi respuesta para ver si CLR fue diseñado intencionalmente de este modo en .NET 1.0.El artículo que cita es sobre una situación diferente (no delegada), que sería una optimización del compilador: reemplazar un 'callvirt' por una llamada directa cuando el tipo de instancia se puede determinar estáticamente. Tenga en cuenta que la razón por la cual CLR tiene que lanzar 'NullReferenceException' durante' callvirt' es la necesidad de una búsqueda VMT basada en la referencia 'this'; no solo un deseo semántico de verificar la referencia. –

+0

Relacionado: http://stackoverflow.com/q/3143498/158779 –

+0

En mi humilde opinión, es desafortunado que no haya una forma estándar (quizás a través de atributos) para especificar que un método debe invocarse con 'call' en lugar de' callvirt', ya que eso hubiera permitido que los tipos que encapsulan valores [como 'cadena'] tengan miembros que puedan operar en ubicaciones de almacenamiento con valores predeterminados. Diciendo 'if (someString.IsNullOrEmpty)' sería mucho más limpio en mi humilde opinión que 'if (String.IsNullOrEmpty (someString))'. – supercat

Respuesta

23

Debido a que estás pasando null en el firstArgument de Delegate.CreateDelegate

Así que usted está llamando a un método de instancia en un objeto nulo.

http://msdn.microsoft.com/en-us/library/74x8f551.aspx

Si firstArgument es una referencia nula y el método es un método de instancia, el resultado depende de las firmas del tipo tipo de delegado y de método:

Si la firma de tipo incluye explícitamente el primer parámetro de método oculto , se dice que el delegado representa un método de instancia abierto.Cuando se invoca al delegado, el primer argumento en la lista de argumentos se pasa al parámetro de instancia oculto del método .

Si las firmas de método y tipo coinciden (es decir, todos los tipos de parámetro son compatibles), se dice que el delegado está cerrado a través de una referencia nula . Invocar al delegado es como llamar a un método de instancia en una instancia nula, lo cual no es particularmente útil para do.

12

Claro que se puede poner en un método si está utilizando la instrucción IL llamada o el enfoque delegado. Solo configurará esta trampa explosiva si intenta acceder a los campos de miembros que le darán la NullReferenceException que buscó.

tratar

int x; 
public void Bar() 
{ 
     x = 1; // NullRefException 
     Debug.Assert(this == null); 
} 

El BCL siquiera contiene explícita esta == cheques nulos para ayudar a la depuración de las lenguas que no utilizan callvirt (como C#) todo el tiempo. Consulte esto question para obtener más información.

La clase String, por ejemplo, tiene tales comprobaciones. No hay nada misterioso en ellos, excepto que no verá la necesidad de ellos en idiomas como C#.

// Determines whether two strings match. 
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)] 
public override bool Equals(Object obj) 
{ 
    //this is necessary to guard against reverse-pinvokes and 
    //other callers who do not use the callvirt instruction 
    if (this == null) 
     throw new NullReferenceException(); 

    String str = obj as String; 
    if (str == null) 
     return false; 

    if (Object.ReferenceEquals(this, obj)) 
     return true; 

    return EqualsHelper(this, str); 
} 
5

Pruebe la documentación para Delegate.CreateDelegate()at msdn.

Está "llamando" manualmente a todo y, por lo tanto, en lugar de pasar una instancia para el puntero this, está pasando nulo. Entonces puede suceder, pero debes intentarlo muy duro.

+0

Vea el siguiente ejemplo de una manera realmente muy fácil. http://bradwilson.typepad.com/blog/2008/01/c-30-extension.html –

+0

los métodos de extensión pueden "verse" como métodos de clase, pero realmente no lo son. Son solo métodos estáticos que actúan en una instancia de clase con alguna facilidad extra de uso incorporada. Entonces no llamaría eso lo mismo. –

+0

Estoy de acuerdo con usted, pero la lista no se detiene con los métodos de extensión. C++/CLI llama directamente a los métodos de C# no virtuales, lo que le proporciona exactamente el mismo código fuente que el ejemplo de los métodos de extensión, esta vez dando como resultado un 'this == null' genuino en el lado C#. –

5

this es una referencia, por lo que no hay problema con que sea null desde la perspectiva del sistema de tipos.

Puede preguntar por qué no se lanzó NullReferenceException. La lista completa de circunstancias cuando CLR arroja esa excepción es documented. Su caso no está en la lista. Sí, es es a callvirt, pero a Delegate.Invoke (see here) en lugar de a Bar, por lo que la referencia this es en realidad su delegado no nulo.

El comportamiento que ve tiene una consecuencia de implementación interesante para CLR. Un delegado tiene una propiedad Target (corresponde a su this referencia) que es bastante frecuente null, concretamente cuando el delegado es estático (imagine Bar sea estático). Ahora existe, naturalmente, un campo de respaldo privado para la propiedad, llamado _target. ¿_target contiene un nulo para un delegado estático? No it doesn't. Contiene una referencia al delegado en sí. ¿Por qué no nulo? Debido a que un nulo es un objetivo legítimo de un delegado como muestra su ejemplo y CLR no tiene dos sabores de un puntero null para distinguir el delegado estático de alguna manera.

Este trozo de trivium demuestra que con los delegados, los objetivos nulos de los métodos de instancia no son una ocurrencia posterior. Es posible que todavía estés haciendo la última pregunta: ¿pero por qué tuvieron que ser apoyados?

El CLR inicial tenía un plan ambicioso de convertirse, entre otros, en la plataforma elegida incluso para desarrolladores Jurados de C++, un objetivo abordado primero con C++ administrado y luego con C++/CLI. Se omitieron algunas características del lenguaje demasiado desafiantes, pero no había nada realmente desafiante en el apoyo a la ejecución de métodos de instancia sin una instancia, lo cual es perfectamente normal en C++. Incluyendo soporte de delegado.

La respuesta final por lo tanto es: porque C# y CLR son dos mundos diferentes.

More good reading y even better reading para mostrar el diseño que permite instancias nulas muestra sus rastros incluso en contextos sintácticos C# muy naturales.

0

esta es una referencia de solo lectura en las clases C#. Por consiguiente, como se esperaba, puede usarse como cualquier otra referencia (en modo de solo lectura) ...

this == null // readonly - possible 
this = new this() // write - not possible 
Cuestiones relacionadas