Подтвердить что ты не робот

Тип 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?

4b9b3361

Ответ 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>.