Тип Nullable не является нулевым типом?
Я тестировал несколько типов с нулевым значением, и он не работал так, как я ожидал:
int? testInt = 0;
Type nullableType = typeof(int?);
Assert.AreEqual(nullableType, testInt.GetType()); // not the same type
Это также не работает:
DateTime? test = new DateTime(434523452345);
Assert.IsTrue(test.GetType() == typeof(Nullable)); //FAIL
DateTime? test = new DateTime(434523452345);
Assert.IsTrue(test.GetType() == typeof(Nullable<>)); //STILL FAIL
Мой вопрос в том, почему testInt.GetType() возвращает int, а typeof (int?) возвращает истинный тип NULL?
Ответы
Ответ 1
В соответствии с MSDN:
Вызов GetType по типу Nullable вызывает операцию бокса выполняется, когда тип неявно преобразован в объект. Поэтому GetType всегда возвращает объект Type, который представляет собой базовый тип, а не тип Nullable.
Когда вы помещаете объект с нулевым значением, вставляется только базовый тип.
Опять же, из MSDN:
Бокс непустой тип значения NULL вставляет тип значения, а не System.Nullable, который обертывает значение тип.
Ответ 2
В дополнение к правильному ответу Ромен, если вы хотите сравнить "реальные" типы (т.е. без неявного преобразования любого типа с нулевым типом в его базовый тип), вы можете создать метод расширения следующим образом:
public static class MyExtensionMethods
{
public static Type GetRealType<T>(this T source)
{
return typeof(T);
}
}
И затем попробуйте следующие тесты:
int? a = 0;
Console.WriteLine(a.GetRealType() == typeof(int?)); // True
Console.WriteLine(a.GetRealType() == typeof(int)); // False
int b = 0;
Console.WriteLine(b.GetRealType() == typeof(int)); // True
Console.WriteLine(b.GetRealType() == typeof(int?)); // False
DateTime? c = DateTime.Now;
Console.WriteLine(c.GetRealType() == typeof(DateTime?)); // True
Console.WriteLine(c.GetRealType() == typeof(DateTime)); // False
DateTime d = DateTime.Now;
Console.WriteLine(d.GetRealType() == typeof(DateTime)); // True
Console.WriteLine(d.GetRealType() == typeof(DateTime?)); // False
ИЗМЕНИТЬ...
Для полноты - и предложено комментариями SLaks ниже - здесь альтернативная версия, которая использует только тип времени компиляции, когда source
является либо null
, либо Nullable<>
; в противном случае он использует GetType
и возвращает тип среды выполнения:
public static class MyExtensionMethods
{
public static Type GetRealType<T>(this T source)
{
Type t = typeof(T);
if ((source == null) || (Nullable.GetUnderlyingType(t) != null))
return t;
return source.GetType();
}
}
Ответ 3
Несмотря на то, что С# делает вид, что в хранилищах типа значений хранятся экземпляры типов, полученных из System.ValueType
, которые, в свою очередь, происходят от System.Object
, это не так. Каждый тип, полученный из System.ValueType
, фактически представляет два очень разных типа вещей:
- Набор байтов, который (для примитивных типов) представляет данные напрямую, или (для не-примитивных типов структуры) содержит содержимое всех полей, общедоступных и частных, но не содержит информации о типе.
- Отдельный объект кучи, который содержит заголовок объекта в дополнение к вышесказанному, тип которого получен из `System.ValueType`.
Место хранения типа значения удерживает первый; кучи объектов типа значения удерживают второй.
По разным причинам Microsoft решила, что Nullable<T>
должна поддерживать только первое использование. Если попытаться передать место хранения типа Nullable<T>
в код, который ожидает ссылку на объект кучи, система преобразует элемент в T
, если HasValue
является истинным, или просто передайте нулевую ссылку, если HasValue
является ложным. Хотя существуют способы создания объекта кучи типа Nullable<T>
, обычные методы преобразования хранилища значений типа в объект кучи никогда не будут генерировать один.
Обратите также внимание на то, что вызов GetType()
в месте хранения значения фактически не будет определять тип места хранения, а вместо этого преобразует содержимое этого места хранения в объект кучи и затем возвращает тип результирующего объекта, Поскольку места хранения типа Nullable<T>
преобразуются либо в экземпляры объекта T
, либо в нуль, ничто в экземпляре объекта не скажет, было ли место хранения, из которого оно было, Nullable<T>
.
Ответ 4
Простой способ проверить, использует ли оператор "is":
(i is Nullable<int>) || (i is Nullable<long>) || (i is Nullable<float>) || (i is Nullable<short>)
Я понял, что читаю эти две страницы MSDN:
http://msdn.microsoft.com/en-us/library/ms366789(v=vs.90).aspx
http://msdn.microsoft.com/en-us/library/ms228597%28v=VS.90%29.aspx
Ура!