Почему IsAssignableFrom возвращает false при сравнении значения nullable с интерфейсом?

Следующий вызов в С# возвращает false:

typeof(IComparable).IsAssignableFrom(typeof(DateTime?))

Тем не менее, следующая строка совершенно верна:

IComparable comparable = (DateTime?)DateTime.Now;

Почему так?

Это потому, что типы с нулевым значением поддерживаются с помощью Nullable<T> и тот факт, что первый общий аргумент реализует интерфейс, не означает, что класс Nullable также реализует этот интерфейс? (например: List<Foo> не реализует интерфейсы, реализуемые Foo)

ИЗМЕНИТЬ: Я думаю, что строка выше компилируется, потому что при боксе с нулевым типом вставляется только базовый тип, как описано здесь: https://msdn.microsoft.com/en-us/library/ms228597.aspx

Ответ 1

Причиной такого поведения является то, что IsAssignableFrom() не учитывает специальные преобразования бокса, которые компилятор испускает для преобразования типов с нулевым значением.

Обратите внимание, что на самом деле вам не нужен бросок в вашем вопросе.

Вместо

 IComparable comparable = (DateTime?)DateTime.Now;

вы можете написать:

DateTime? test = DateTime.Now;
IComparable comparable = test;

Первая из этих строк компилируется, потому что Nullable<T> предоставляет оператор неявного преобразования:

public static implicit operator Nullable<T> (
    T value
)

Вторая строка заставляет компилятор испускать командную строку:

L_000e: box [mscorlib]System.Nullable`1<valuetype [mscorlib]System.DateTime>

Эта операция по боксу описана в разделе 6.1.7 спецификации языка С#, Конверсии бокса (это касается бокс-конверсий для типов с нулевым типом), в котором говорится:

Преобразование бокса позволяет неявно преобразовывать тип значения в ссылочный тип. Конверсия бокса существует из любого non-nullable-value-type для объекта и динамический, для System.ValueType и к любому типу интерфейса, реализованному с помощью типа с нулевым значением. Кроме того, тип перечисления может быть преобразован в тип System.Enum.

Конверсия бокса существует от типа nullable-type к ссылочному типу, если и только если существует конверсия бокса из основного тип non-nullable-value для ссылочного типа.

Тип значения имеет преобразование бокса в тип интерфейса I, если он имеет преобразование в бокс к типу интерфейса I0 и I0 имеет преобразование идентичности в I.

Тип значения имеет преобразование бокса в тип интерфейса I, если он имеет преобразование бокса в интерфейс или делегат типа I0 и I0 (§13.1.3.2) к I.

Бокс - значение типа, отличного от nullable-value, состоит из выделения экземпляра объекта и копируя значение типа value в этот экземпляр. Структуру можно вставить в коробку к типу System.ValueType, поскольку это базовый класс для всех структур (§11.3.2).

Это то, что приводит к операции по боксу выше. Я выделил выделение и выделил курсивом наиболее подходящий момент.

Также см. ссылку (поставляемую OP): https://msdn.microsoft.com/en-us/library/ms228597.aspx

Ответ 2

Неверные являются специальными.

object boxedNullable = new decimal?(42M);

Console.WriteLine(boxedNullable.GetType().Name); // Decimal

Когда вы устанавливаете значение с нулевым значением, то, что вы на самом деле делаете, - это полевое значение, а не значение null. Таким образом, default(decimal?) предоставит вам только null (а не значение "null-value nullable" ), а new decimal?(42M) предоставит вам коробку decimal.

Когда вы вводите тип значения в интерфейс, он должен быть помещен в коробку - так что ваша вторая строка фактически изменяет значение null на коробку DateTime. DateTime? не реализует IComparable, но DateTime делает - и это то, что вы в конечном итоге используете для интерфейса, потому что сначала должен быть указан тип значения.

Это определено в спецификации ECMA, I.8.2.4 Boxing and unboxing of values:

Все типы значений имеют операцию, называемую полем. Бокс - значение любого типа значений дает его бокс-значение; то есть значение соответствующего типа в боксе, содержащего побитую копию исходного значения. Если тип значения является нулевым типом, определяемым как экземпляр типа значения System.Nullable<T>, результатом является нулевая ссылка или побитовая копия его свойства Value типа T в зависимости от его свойства HasValue (соответственно false и true), Все типы в коробке имеют операцию с именем unbox, что приводит к управляемому указателю на представление битов значения.

Лучше всего использовать либо x is IComparable, либо x as IComparable, чтобы найти, если какой-то тип реализует IComparable; используя рефлексию, раскрывает вам множество крошечных причуд как .NET, так и С# (и любого другого языка, который использовал тот, кто написал код).