C#中的以下调用返回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
这种行为的原因是 IsAssignableFrom()
不考虑编译器为可空类型的转换发出的特殊装箱转换。
请注意,您实际上并不需要在问题中使用强制转换。
代替
IComparable comparable = (DateTime?)DateTime.Now;
你可以写:
DateTime? test = DateTime.Now;
IComparable comparable = test;
这些行中的第一行编译是因为 Nullable<T>
提供隐式转换运算符:
public static implicit operator Nullable<T> (
T value
)
第二行导致编译器发出一个box指令:
L_000e: box [mscorlib]System.Nullable`1<valuetype [mscorlib]System.DateTime>
这个装箱操作由C#语言规范的第6.1.7节涵盖, 拳击转换 (这包括专门针对可空类型的装箱转换),其中指出:
装箱转换允许将值类型隐式转换为
参考类型。任何一个拳击转换都存在
对象和动态的非可空值类型到System.ValueType 和
到非可空值类型实现的任何接口类型。
此外,枚举类型可以转换为System.Enum类型。
如果存在从可空类型到引用类型的装箱转换
并且仅当底层存在拳击转换时
非可空值类型到引用类型。
如果值类型具有装箱转换,则它具有到接口类型I的装箱转换
到接口类型I0和I0有一个身份转换为I.
值类型具有到接口类型I的装箱转换(如果它具有)
拳击转换为接口或委托类型I0和I0是
方差可转换(§13.1.3.2)到I.
拳击非可空值类型的值包括分配对象实例和
将值类型值复制到该实例中。结构可以装箱
到System.ValueType类型,因为它是所有的基类
结构(第11.3.2节)。
这就是导致上面拳击行动的原因。我用粗体和斜体来表达最相关的观点。
另请参阅此链接(由OP提供): https://msdn.microsoft.com/en-us/library/ms228597.aspx
Nullables很特别。
object boxedNullable = new decimal?(42M);
Console.WriteLine(boxedNullable.GetType().Name); // Decimal
当你打开一个可以为空的值时,你实际做的是打包底层值,而不是可空。所以 default(decimal?)
会给你的 null
(而不是“无价值可空”),和 new decimal?(42M)
会给你一个盒装 decimal
。
将值类型转换为接口时,它 必须盒装 - 所以你的第二行实际上将可空的nullable改为盒装 DateTime
。 DateTime?
没有实现 IComparable
但是 DateTime
确实 - 这就是你最终投射到界面的内容,因为必须首先装箱值类型。
这在ECMA规范中定义, I.8.2.4 Boxing and unboxing of values
:
所有值类型都有一个名为box的操作。装箱任何值类型的值会产生其盒装值;即,包含原始值的按位副本的相应盒装类型的值。如果值类型是可空类型 - 定义为值类型的实例化 System.Nullable<T>
- 结果是类型为T的Value属性的空引用或按位副本,具体取决于其HasValue属性(分别为false和true)。所有盒装类型都有一个名为unbox的操作,它会生成一个指向值的位表示形式的托管指针。
你最好的选择是使用其中之一 x is IComparable
要么 x as IComparable
找到某种类型的实现 IComparable
;使用反射让你暴露出很多.NET和C#的小怪癖(以及编写代码的人使用的其他任何语言)。