La referencia de objeto no se establece en una instancia de un objeto. ¿Por qué .NET no muestra qué objeto es "nulo"?

103

Con respecto a este mensaje de excepción no controlada de .NET:

Referencia a objeto no establecida como instancia de un objeto.

¿Por qué .NET no muestra qué objeto es null?

Sé que puedo verificar nully resolver el error. Sin embargo, ¿por qué .NET no ayuda a señalar qué objeto tiene una referencia nula y qué expresión desencadenó la NullReferenceException?


fuente
2
Cuando esto suceda, vuelva a escribir la línea en la que sucedió para que primero verifique cada resultado posible en busca de nulos; luego sabrá exactamente qué fue. O eso, o tener adjunto el increíble depurador de Visual Studio, que se interrumpe en el instante en que ocurre una excepción y le permite ver lo que es nulo :)
Patashu
5
Realmente no, simplemente pregunta por qué .NET framework no ayuda al programador a mostrar qué objeto es nulo. Supongo que es la penalización del rendimiento (necesitarías reflexión). pero tampoco estoy seguro.
bas
1
@bas: Si bien eso es cierto, la pregunta es un poco engañosa ya que debería ser sobre una "parte de una expresión", no un "objeto". Eso también explica por qué la mera reflexión no ayudará, pero se requerirá cierta información de depuración extensa.
OR Mapper
4
Todavía tengo curiosidad por la respuesta. Es algo similar a las excepciones .net que no ayudan a señalar qué clave no existe en un diccionario. Además, no entiendo los devotos de la pregunta.
bas
12
Terminología, por favor: un objeto nunca es nulo. Sin embargo, podría ser una referencia de objeto . Pero una referencia de objeto es solo una ubicación en la memoria, ¿cómo le ayudaría, a menos que tenga un depurador adjunto de todos modos?
Oskar Berggren

Respuestas:

169

(Para obtener información sobre el nuevo asistente de excepción en Visual Studio 2017, consulte el final de esta respuesta)


Considere este código:

String s = null;
Console.WriteLine(s.Length);

Esto arrojará un NullReferenceExceptionen la segunda línea y querrá saber por qué .NET no le dice sque era nulo cuando se lanzó la excepción.

Para entender por qué no obtiene esa información, debe recordar que no es la fuente C # la que se ejecuta, sino IL:

IL_0001: ldnull      
IL_0002: stloc.0 // s
IL_0003: ldloc.0 // s
IL_0004: callvirt System.String.get_Length
IL_0009: llamar a System.Console.WriteLine

Es el callvirtcódigo de operación el que arroja el NullReferenceExceptiony lo hace cuando el primer argumento en la pila de evaluación es una referencia nula (la que se cargó usando ldloc.0).

Si .NET debería poder decir que sfue una referencia nula, debería de alguna manera rastrear que el primer argumento en la pila de evaluación se originó en el formulario s. En este caso, es fácil para nosotros ver que es snulo, pero ¿qué pasa si el valor es un valor de retorno de otra llamada de función y no se almacena en ninguna variable? De todos modos, este tipo de información no es lo que desea rastrear en una máquina virtual como la máquina virtual .NET.


Para evitar este problema, le sugiero que realice una verificación nula de argumentos en todas las llamadas a métodos públicos (a menos que, por supuesto, permita la referencia nula):

public void Foo(String s) {
  if (s == null)
    throw new ArgumentNullException("s");
  Console.WriteLine(s.Length);
}

Si se pasa nulo al método, obtiene una excepción que describe con precisión cuál es el problema (que ses nulo).


Cuatro años después, Visual Studio 2017 ahora tiene un nuevo asistente de excepción que intentará decir qué es nulo cuando NullReferenceExceptionse lanza un. Incluso es capaz de brindarle la información requerida cuando es el valor de retorno de un método que es nulo:

Asistente de excepción de Visual Studio 2017

Tenga en cuenta que esto solo funciona en una compilación DEBUG.

Martin Liversage
fuente
5
Los números de línea y los nombres de los archivos de origen tampoco se almacenan en el código IL en sí, ¿o sí? Sin embargo, pueden estar disponibles para depuración.
OR Mapper
4
@MartinLiversage: Exactamente. Entonces, la pregunta se reduce a: ¿Por qué no hay suficiente información almacenada en los archivos de símbolos que también indique qué expresión del código se evaluó null?
OR Mapper
2
@MartinLiversage: Los archivos de símbolos son accesibles de tal manera que, en caso de una excepción, junto con el mensaje de excepción, el archivo de origen y el número de línea se pueden mostrar en la salida del depurador. Entonces, la pregunta es, ¿cuál es la razón para no incluir más información sobre lo que se devolvió exactamente null? Tenga en cuenta que el OP no afirma que quiera saber que para las versiones de lanzamiento, las versiones de depuración pueden ser suficientes.
OR Mapper
3
Hmm, y no debemos olvidar que ni siquiera es el IL el que se ejecuta, sino el código nativo creado a partir de él en tiempo de ejecución.
Oskar Berggren
2
@MartinLiversage: Nadie en esta pregunta afirma que queremos que esto sea perfectamente compatible para las versiones de lanzamiento. De todos modos, no veo el problema de correlacionar el código de operación IL que usa una referencia de objeto (que puede resultar ser null) con la línea y columna del archivo fuente que devolvió esa referencia de objeto.
OR Mapper
9

¿Cómo quiere que se vea el mensaje de error en el siguiente caso?

AnyObject.GetANullObject().ToString();

private object GetANullObject()
{
  return null;
}

¡No hay nombres de variables para informar aquí!

romar
fuente
2
Sospecho que el OP está buscando la expresión en el código fuente que devuelve nulo, no el objeto. Agregué un comentario respectivo a la pregunta y espero que él o ella aclare las cosas. Si mi sospecha es correcta, el OP esperaría algo Object reference obtained from AnyObject.GetANullObject() not set to an instance of an object.como el mensaje de error.
OR Mapper
1
@ORMapper Estoy de acuerdo. ¡Habría puesto mi "respuesta" en un comentario al OP, si tuviera suficientes puntos de reputación para agregar un comentario!
romar
1
"Una referencia nula intentó llamar al método ToString () de la Clase XYZ" sería más útil de lo que tenemos ahora.
Michael Levy
Lo más útil sería un seguimiento de la pila que muestre, en cada nivel de llamada, exactamente qué línea, en qué archivo, produjo el error. Oh, espera ... ¡eso es lo que hace ahora!
Jim Balter
1

Bueno, eso depende de los ingenieros de Microsoft. Pero, obviamente, puede usar un depurador y agregar un reloj para descubrir cuál de ellos tiene un problema.

Sin embargo, la excepción es lo NullReferenceExceptionque significa que la referencia no existe . No puede obtener el objeto que no se ha creado en absoluto.

but why .NET don't tell us which object is null? Porque no sabe qué objeto es nulo. ¡El objeto simplemente no existe!

Lo mismo ocurre cuando digo que C # se compila en código .NET IL. El código .NET IL no conoce los nombres o expresiones. Solo conoce referencias y su ubicación. Aquí tampoco se puede obtener lo que no existe. La expresión o el nombre de la variable no existe.

Filosofía: No puedes hacer una tortilla si no tienes un huevo en primer lugar.

Aniket Inge
fuente
3
esa tampoco es una respuesta :)
bas
¿Cómo se obtiene la referencia si no existe? @Bas
Aniket Inge
4
"Bueno, eso depende de los ingenieros de Microsoft para responder". Así que permítales arrojar una luz sobre esto en lugar de decir lo obvio
bas
@bas podemos decidir qué es lógico al menos. Lógicamente el objeto no existe. ¿Cómo lo detectará con una excepción y luego imprimirá el nombre del objeto? Simplemente no existe. Ni siquiera en la pila ..
Aniket Inge
entonces la respuesta es que es virtualmente imposible señalar qué objeto tiene una referencia nula. Esa también es una respuesta. No estoy diciendo que lo sé, solo me gusta la pregunta :). +1 por todo el esfuerzo: p
bas
1

No estoy seguro, pero esto puede deberse a que .Net no sabe si es una clase predefinida o definida por el usuario. Si está predefinido, entonces puede ser nulo (como una cadena que ocupa 2 Bytes) pero si está definido por el usuario, tenemos que crear una instancia para que sepa que este objeto ocupará esta cantidad de memoria. Por lo tanto, arroja un error en tiempo de ejecución.

oniel telies
fuente
-2

Buena pregunta. El cuadro de mensaje es casi inútil. Incluso si está enterrado a una milla de profundidad de la definición de referencias, alguna clase o ensamblado o archivo u otra información sería mejor que lo que proporcionan actualmente (lea: mejor que nada).

Su mejor opción es ejecutarlo en el depurador con información de depuración, y su IDE se romperá en la línea ofensiva (lo que demuestra claramente que, de hecho, hay información útil disponible).

Rick O'Shea
fuente