Почему 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, так и С# (и любого другого языка, который использовал тот, кто написал код).