Рассмотрим следующий код:
int? x = null;
Console.Write ("Hashcode: ");
Console.WriteLine(x.GetHashCode());
Console.Write("Type: ");
Console.WriteLine(x.GetType());
При выполнении он пишет, что Hashcode - это 0
, но терпит неудачу с NullReferenceException
при попытке определить тип x
.
Я знаю, что методы, вызываемые для типов, допускающих значение NULL, на самом деле вызываются для базовых значений, поэтому я ожидал, что программа выйдет из строя во время x.GetHashCode()
.
Итак, в чем принципиальная разница между этими двумя методами и почему первый из них не дает сбоев?
Мне кажется странным, что GetType()
из Nullable<int>
возвращает System.Int32
, а не System.Nullable<System.Int32>
.
Также стоит отметить, что int? x = null
является синтаксическим сахаром для Nullable<int> x = new Nullable<int>(null)
. Итак, x
- это реальный объект, а не пустая ссылка.
Справочный источник - github.com/Microsoft/referencesource/blob/master/mscorlib/… - никоим образом не показывает, что GetType обрабатывается, а также не подробно это в документации - docs.microsoft.com/en-us/dotnet/api/…
Похоже, GetHashCode получил нулевую проверку. (Используется JetBrains для просмотра определения)
public override int GetHashCode()
{
if (!this.hasValue)
return 0;
return this.value.GetHashCode();
}
Реализация GetHashCode()
вступает в игру только после того, как будет установлено, что GetHashCode()
вообще можно вызывать.
Реализация 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, а не для значения, которое он содержит? Почему так?
Почему x.GetType()
- это то же самое, что и null.GetType()
, если на самом деле у вас есть (struct).GetType()
? Nullable<T>
- это структура, тип значения.
Можете ли вы также опубликовать реализацию GetType
на всякий случай, если она добавит больше деталей к ответу?
Это связано с тем, что int? x = null;
по существу создает экземпляр типа значения System.Nullable<int>
с «внутренним» значением null
(вы можете проверить его через свойство .HasVaue
). Когда вызывается GetHashCode
, переопределение Nullable<int>.GetHashCode
является кандидатом метода (поскольку метод виртуальный), теперь у нас есть экземпляр Nullable<int>
, и мы отлично выполняем его метод экземпляра.
При вызове GetType
метод не является виртуальным, поэтому экземпляр Nullable<int>
сначала помещается в System.Object
в соответствии с документ, а значение в коробке - null
, следовательно, NullReferenceException
.
Все это соответствует тому поведению, которое мы видим, но где это задокументировано? На странице, на которую вы ссылаетесь, подробно описано, как работает бокс для Nullable<T>
, но там не сказано, что для GetType()
потребуется операция бокса. Я предполагаю, что нам нужно найти документацию о том, как унаследованные методы работают с типами значений, чтобы найти соответствующие части.
@ LasseVågsætherKarlsen, потому что GetType
определен на object
вместо struct
, что вызывает неявную упаковку. Вы можете проверить, назначив int? x = 1
, а затем позвонив в GetType()
. Результат - System.Int32
, нетNullable<T>
.
@ LasseVågsætherKarlsen Вы можете прочитать раздел 4.3 спецификации языка C# «Упаковка и распаковка» для получения более подробной информации.
Если я наберу int? х = ноль; а затем я пытаюсь прочитать это: x.Value, тогда я получаю исключение, а не нулевое "значение". Если я проверю, является ли само «x» нулевым, то ответ будет положительным.
@Eru Я использовал неправильные выражения в ответе, спасибо, что указали на это.
@DannyChen Хорошо, но каким должен быть правильный ответ на это?
@Eru Правильный ответ на какой вопрос?
@DannyChen x.HasValue это ложь; x.GetHashCode равен 0; x.GetType возвращает System.NullReferenceException; x.GetValueOrDefault () также равен 0; Так что еще я могу с этим сделать?
@Eru Я не совсем понял, давайте переместим обсуждение на эта чат-комната и, пожалуйста, снова опишите свои вопросы там.
Чтобы прояснить правильный ответ Дэнни Чена:
Nullable<T>
- это тип значения. Тип значения состоит из bool, указывающего на нулевое значение (false означает null), и T, значения.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: Просто, когда у вас есть 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
переменной.
Единственное отличие, которое я могу найти, заключается в том, что
GetHashCode
- этоvirtual
...