【问题标题】:Why IsAssignableFrom return false when comparing a nullable against an interface?为什么 IsAssignableFrom 在将可空值与接口进行比较时返回 false?
【发布时间】:2017-02-18 17:32:57
【问题描述】:

以下 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

【问题讨论】:

  • 有趣的问题是为什么即使Nullable<T> 没有实现IComparable,你的第二行也会编译
  • 我应该重写这个问题。
  • 您提供的链接已经很好地解释了它,甚至显示了一个可以分配给IConvertible 的可空示例。所以原因是,如果一个 nullable 有一个值,它将被装箱到底层类型T,而不是 nullable。

标签: c# types


【解决方案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>

C# 语言规范的第 6.1.7 节装箱转换(这专门针对可空类型的装箱转换)涵盖了此装箱操作,其中指出:

装箱转换允许将值类型隐式转换为 一个引用类型。拳击转换存在于任何 non-nullable-value-type 到 object 和 dynamic,到 System.ValueType and 到由 non-nullable-value-type 实现的任何接口类型。 此外,枚举类型可以转换为 System.Enum 类型。

存在从可空类型到引用类型的装箱转换,如果 并且仅当底层证券存在装箱转换时 non-nullable-value-type 到引用类型。

一个值类型有一个装箱转换到一个接口类型 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

【讨论】:

  • 但是为什么当我不转换为Nullable&lt;T&gt;T 时使用它,但是当我将它分配给T 实现的接口时?所以它确实回答了一个问题,为什么我可以使用DateTime.Now 并将其分配给DateTime? 变量,但它没有解释(?)为什么我可以为T 实现的接口分配可空值。
  • @TimSchmelter 确实,这是一个不同的问题。 :)
  • 这并不是一个真正不同的问题,因为只有当 OP 尝试时,您的答案才能解释它:typeof(DateTime).IsAssignableFrom(typeof(DateTime?));(也将返回 false)。但实际上他正试图将其分配给IComparable
  • 其实 OP 已经用他提供的the link 回答了这个问题。带有值的 Nullable 将被装箱到基础类型 T 而不是 nullable。
  • @TimSchmelter 我添加了对 C# 标准相关部分的引用;为了完整起见,我还将添加 OP 的链接。
【解决方案2】:

Nullables 是特殊的。

object boxedNullable = new decimal?(42M);

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

当您装箱一个可为空的值时,您实际上所做的是装箱基础值,而不是可为空的。所以default(decimal?) 只会给你null(而不是“无值可空”),new decimal?(42M) 会给你一个盒装的decimal

当您将值类型转换为接口时,必须装箱 - 因此您的第二行实际上将可空值更改为装箱 DateTimeDateTime? 没有实现 IComparable,但 DateTime 实现了 - 这就是您最终要转换到接口的内容,因为值类型必须首先装箱。

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

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

最好的办法是使用x is IComparablex as IComparable 来查找某些类型是否实现IComparable;使用反射会让您接触到 .NET 和 C#(以及编写代码的人使用的任何其他语言)的许多小怪癖。

【讨论】:

    猜你喜欢
    • 2016-12-22
    • 2020-07-29
    • 2023-01-24
    • 1970-01-01
    • 2020-02-11
    • 1970-01-01
    • 2017-04-22
    • 2011-05-22
    • 2017-11-04
    相关资源
    最近更新 更多