为什么IsAssignableFrom在将nullable与接口进行比较时返回false?

时间:2016-10-10 10:04:15

标签: c# types

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

2 个答案:

答案 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>

当您将值类型转换为接口时,必须被装箱 - 因此您的第二行实际上将可空值更改为装箱DateTimeDateTime?没有实现IComparable,但DateTime会执行 - 而且这是您最终投射到界面的内容,因为值类型必须加框第一

这在ECMA规范I.8.2.4 Boxing and unboxing of values中定义:

  

所有值类型都有一个名为box的操作。装箱任何值类型的值会产生其盒装值;即,包含原始值的按位副本的相应盒装类型的值。如果值类型是可空类型 - 定义为值类型System.Nullable<T>的实例化 - 结果是其类型为T的Value属性的空引用或按位副本,具体取决于其HasValue属性(false和true,分别)。所有盒装类型都有一个名为unbox的操作,它会生成一个指向值的位表示形式的托管指针。

您最好的选择是使用x is IComparablex as IComparable来查找某些类型是否IComparable;使用反射让你暴露出很多.NET和C#的小怪癖(以及编写代码的人使用的其他任何语言)。