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
答案 0 :(得分:11)
此行为的原因是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
答案 1 :(得分:3)
Nullables很特别。
object boxedNullable = new decimal?(42M);
Console.WriteLine(boxedNullable.GetType().Name); // Decimal
当您设置可以为空的值时,您实际执行的操作是将基础值包装,而不是可为空。因此,default(decimal?)
只会为您提供null
(而不是&#34;无价值可空和#34;),new decimal?(42M)
将为您提供一个盒装decimal
。< / p>
当您将值类型转换为接口时,必须被装箱 - 因此您的第二行实际上将可空值更改为装箱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#的小怪癖(以及编写代码的人使用的其他任何语言)。