Когда именно типы, допускающие значение NULL, вызывают исключения?

Рассмотрим следующий код:

int? x = null;
Console.Write ("Hashcode: ");
Console.WriteLine(x.GetHashCode());
Console.Write("Type: ");
Console.WriteLine(x.GetType());

При выполнении он пишет, что Hashcode - это 0, но терпит неудачу с NullReferenceException при попытке определить тип x. Я знаю, что методы, вызываемые для типов, допускающих значение NULL, на самом деле вызываются для базовых значений, поэтому я ожидал, что программа выйдет из строя во время x.GetHashCode().

Итак, в чем принципиальная разница между этими двумя методами и почему первый из них не дает сбоев?

Единственное отличие, которое я могу найти, заключается в том, что GetHashCode - это virtual ...

Bart Friederichs 02.05.2018 08:34
ILSpy - это удобный небольшой инструмент, который поможет ответить на подобные вопросы.
Sam Axe 02.05.2018 08:36

Мне кажется странным, что GetType() из Nullable<int> возвращает System.Int32, а не System.Nullable<System.Int32>.

Lasse V. Karlsen 02.05.2018 08:36

Также стоит отметить, что int? x = null является синтаксическим сахаром для Nullable<int> x = new Nullable<int>(null). Итак, x - это реальный объект, а не пустая ссылка.

Bart Friederichs 02.05.2018 08:46

Справочный источник - github.com/Microsoft/referencesource/blob/master/mscorlib/… - никоим образом не показывает, что GetType обрабатывается, а также не подробно это в документации - docs.microsoft.com/en-us/dotnet/api/…

Lasse V. Karlsen 02.05.2018 08:49
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
30
5
2 766
4
Перейти к ответу Данный вопрос помечен как решенный

Ответы 4

Похоже, GetHashCode получил нулевую проверку. (Используется JetBrains для просмотра определения)

public override int GetHashCode()
{
  if (!this.hasValue)
    return 0;
  return this.value.GetHashCode();
}

Реализация GetHashCode() вступает в игру только после того, как будет установлено, что GetHashCode() вообще можно вызывать.

user743382 02.05.2018 22:48

Реализация Nullable<T>.GetHashCode() выглядит следующим образом:

public override int GetHashCode()
{
    if (!this.HasValue)
    {
        return 0;
    }
    return this.value.GetHashCode();
}

Итак, когда значение равно нулю, вы всегда получите 0.

x.GetType() такой же, как null.GetType(), который выбрасывает Object reference not set to an instance of an object

Итак, GetHashCode вызывается для Nullable, а не для значения, которое он содержит? Почему так?

Кирилл Глазырин 02.05.2018 08:40

Почему x.GetType() - это то же самое, что и null.GetType(), если на самом деле у вас есть (struct).GetType()? Nullable<T> - это структура, тип значения.

Lasse V. Karlsen 02.05.2018 08:50

Можете ли вы также опубликовать реализацию GetType на всякий случай, если она добавит больше деталей к ответу?

Souvik Ghosh 02.05.2018 10:00
Ответ принят как подходящий

Это связано с тем, что int? x = null; по существу создает экземпляр типа значения System.Nullable<int> с «внутренним» значением null (вы можете проверить его через свойство .HasVaue). Когда вызывается GetHashCode, переопределение Nullable<int>.GetHashCode является кандидатом метода (поскольку метод виртуальный), теперь у нас есть экземпляр Nullable<int>, и мы отлично выполняем его метод экземпляра.

При вызове GetType метод не является виртуальным, поэтому экземпляр Nullable<int> сначала помещается в System.Object в соответствии с документ, а значение в коробке - null, следовательно, NullReferenceException.

Все это соответствует тому поведению, которое мы видим, но где это задокументировано? На странице, на которую вы ссылаетесь, подробно описано, как работает бокс для Nullable<T>, но там не сказано, что для GetType() потребуется операция бокса. Я предполагаю, что нам нужно найти документацию о том, как унаследованные методы работают с типами значений, чтобы найти соответствующие части.

Lasse V. Karlsen 02.05.2018 09:16

@ LasseVågsætherKarlsen, потому что GetType определен на object вместо struct, что вызывает неявную упаковку. Вы можете проверить, назначив int? x = 1, а затем позвонив в GetType(). Результат - System.Int32, нетNullable<T>.

Toxantron 02.05.2018 09:34

@ LasseVågsætherKarlsen Вы можете прочитать раздел 4.3 спецификации языка C# «Упаковка и распаковка» для получения более подробной информации.

Cheng Chen 02.05.2018 10:25

Если я наберу int? х = ноль; а затем я пытаюсь прочитать это: x.Value, тогда я получаю исключение, а не нулевое "значение". Если я проверю, является ли само «x» нулевым, то ответ будет положительным.

Eru 08.05.2018 21:14

@Eru Я использовал неправильные выражения в ответе, спасибо, что указали на это.

Cheng Chen 09.05.2018 05:02

@DannyChen Хорошо, но каким должен быть правильный ответ на это?

Eru 09.05.2018 17:35

@Eru Правильный ответ на какой вопрос?

Cheng Chen 10.05.2018 03:59

@DannyChen x.HasValue это ложь; x.GetHashCode равен 0; x.GetType возвращает System.NullReferenceException; x.GetValueOrDefault () также равен 0; Так что еще я могу с этим сделать?

Eru 10.05.2018 10:11

@Eru Я не совсем понял, давайте переместим обсуждение на эта чат-комната и, пожалуйста, снова опишите свои вопросы там.

Cheng Chen 11.05.2018 04:24

Чтобы прояснить правильный ответ Дэнни Чена:

  • Nullable<T> - это тип значения. Тип значения состоит из bool, указывающего на нулевое значение (false означает null), и T, значения.
  • В отличие от всех других типов значений, типы, допускающие значение NULL, не упаковываются в коробку Nullable<T>. Они упаковывают либо T в штучной упаковке, либо пустую ссылку.
  • Метод, реализованный типом значения S, реализован так, как если бы он имел невидимый аргумент ref S; так передается this.
  • Метод, реализованный ссылочным типом C, реализован так, как если бы был невидимый аргумент C; так передается this.
  • Тогда интересным случаем является виртуальный метод, определенный в эталонном базовом классе и переопределенный структурой, наследуемой от базового класса.

Теперь у вас достаточно информации, чтобы понять, что происходит. GetHashCode - это виртуальный и заменен Nullable<T>, поэтому, когда вы вызываете его, вы вызываете его так, как если бы для ref Nullable<T> был невидимый аргумент this. Бокса не бывает.

GetType не является виртуальным, поэтому не может быть переопределен и определен на object. Следовательно, он ожидает object для this. При вызове на Nullable<T> приемник должен быть упакован в коробку и, следовательно, может быть обнулен, и, следовательно, может отбрасывать.

Если вы позвонили в ((object)x).GetHashCode(), то увидите исключение.

Третий и четвертый пункты о невидимых ref S и C немного неясны. Можете ли вы рассказать о них подробнее?

Nisarg 02.05.2018 13:26

@Nisarg: Просто, когда у вас есть class C { void M(int x) { ... } } и есть C c = new C(); c.M(123);, тогда каким-то образом c должен превратиться в this внутри M. Это происходит так, что вызов логически такой же, как если бы у вас был class C { static void M(C _this, int x) { ... }} и вызов C.M(c, 123). this логически является просто еще один аргумент. Аналогично для структур, за исключением структур this - это псевдоним ref переменной.

Eric Lippert 02.05.2018 16:26

Другие вопросы по теме