Ссылка на объект не установлена ​​в экземпляр объекта. Почему .NET не показывает, какой объект является "нулевым"?

Относительно этого сообщения необработанного исключения .NET:

Ссылка на объект не установлена ​​в экземпляр объекта.

Почему .NET не показывает, какой объект null?

Я знаю, что могу проверить null и разрешить ошибку. Однако почему .NET не указывает, какой объект имеет нулевую ссылку и какое выражение вызвало NullReferenceException?

Ответ 1

(Для получения информации о новом помощнике исключения в Visual Studio 2017 см. конец этого ответа)


Рассмотрим этот код:

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

Это вызовет NullReferenceException во второй строке, и вы хотите знать, почему .NET не говорит вам, что это было s, когда было выбрано исключение.

Чтобы понять, почему вы не получаете эту информацию, вы должны помнить, что это не источник С#, а IL:

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

Это код операции callvirt, который выдает NullReferenceException, и он делает это, когда первый аргумент в стеке оценки является пустой ссылкой (той, которая была загружена с помощью ldloc.0).

Если .NET должен иметь возможность сказать, что это была s, которая была нулевой ссылкой, она каким-то образом должна отслеживать, что первый аргумент в стеке оценки возник из формы s. В этом случае нам легко видеть, что оно s было нулевым, но что, если значение было возвратным значением из другого вызова функции и не хранилось ни в какой переменной? В любом случае, такая информация не является тем, что вы хотите отслеживать на виртуальной машине, например, на виртуальной машине .NET.


Чтобы избежать этой проблемы, я предлагаю вам выполнить проверку нулевого аргумента во всех общедоступных вызовах методов (если, конечно, вы не разрешаете нулевую ссылку):

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

Если null передан методу, вы получаете исключение, которое точно описывает, что проблема (что s равно null).


Спустя четыре года Visual Studio 2017 теперь имеет нового помощника исключения, который попытается рассказать, что является нулевым при вызове NullReferenceException. Он даже способен предоставить вам требуемую информацию, когда это возвращаемое значение метода, который имеет значение null:

Помощник исключения Visual Studio 2017

Обратите внимание, что это работает только в сборке DEBUG.

Ответ 2

Как вы хотите, чтобы сообщение об ошибке в следующем случае выглядело?

AnyObject.GetANullObject().ToString();

private object GetANullObject()
{
  return null;
}

Здесь нет имен переменных!

Ответ 3

Хорошо, что до инженеров в Microsoft ответить. Но вы, очевидно, можете использовать отладчик и добавлять часы, чтобы выяснить, у кого из них есть проблема.

Однако исключение составляет NullReferenceException, что означает, что ссылка не существует. Вы не можете получить объект, который не был создан вообще.

but why .NET don't tell us which object is null? Потому что он не знает, какой объект имеет значение null. Объект просто не существует!

То же самое происходит, когда я говорю, С# скомпилирован в .NET IL-код. Код .NET IL не знает имена или выражения. Он знает только ссылки и их местоположение. Здесь тоже вы не можете получить то, чего не существует. Выражение или имя переменной не существует.

Философия: вы не можете сделать омлет, если у вас нет яйца в первую очередь.

Ответ 4

Хороший вопрос. Коробка сообщений просто не нужна. Даже если он зарыт в миле от определения ссылок, какой-то класс или сборка или файл или другая информация будут лучше, чем то, что они в настоящее время предоставляют (читай: лучше, чем ничего).

Ваш лучший вариант - запустить его в отладчике с информацией об отладке, и ваша среда IDE сломается на оскорбительной строке (довольно четко демонстрируя, что полезная информация фактически доступна).