Nullable 的装箱/拆箱行为怎么可能?

我今天早些时候发生的事情让我摸不着头脑。

任何Nullable类型的变量都可以赋值为null 。 例如:

 int? i = null; 

起初我无法看到如果不以某种方式定义从objectNullable的隐式转换,这将是如何可能的:

 public static implicit operator Nullable(object box); 

但是上面的运算符显然不存在,好像它确实如此,那么以下内容也必须是合法的,至少在编译时(它不是):

 int? i = new object(); 

然后我意识到也许Nullable类型可以定义一个隐式转换到一些永远无法实例化的任意引用类型,如下所示:

 public abstract class DummyBox { private DummyBox() { } } public struct Nullable where T : struct { public static implicit operator Nullable(DummyBox box) { if (box == null) { return new Nullable(); } // This should never be possible, as a DummyBox cannot be instantiated. throw new InvalidCastException(); } } 

但是,这并不能解释我接下来会发生什么:如果任何Nullable值的HasValue属性为false ,那么该值将被设置为null

 int? i = new int?(); object x = i; // Now x is null. 

此外,如果HasValuetrue那么该值将被设置为T而不是T?

 int? i = 5; object x = i; // Now x is a boxed int, NOT a boxed Nullable. 

但这似乎意味着存在从Nullableobject的自定义隐式转换:

 public static implicit operator object(Nullable value); 

显然不是这种情况,因为object是所有类型的基类,用户定义的基本类型的隐式转换是非法的(它们应该是非法的)。

object x = i;似乎object x = i; i应该喜欢任何其他值类型,以便x.GetType()产生与typeof(int?)相同的结果(而不是抛出NullReferenceException )。

所以我挖了一下,果然,事实certificate这种行为特定于Nullable类型,在C#和VB.NET规范中特别定义,并且在任何用户定义的struct (C#)中都不可重现或Structure (VB.NET)。

这就是为什么我仍然感到困惑。

这种特殊的装箱和拆箱行为似乎无法手工实施。 它只能起作用,因为C#和VB.NET都对Nullable类型进行了特殊处理。

  1. 从理论上讲,在Nullable没有得到这种特殊处理的情况下,可能存在不同的基于CLI的语言吗? 因此, Nullable类型不会在不同语言中表现出不同的行为吗?

  2. C#和VB.NET如何实现这种行为? 它是否受到CLR的支持? (也就是说,CLR是否允许类型以某种方式“覆盖”它的装箱方式,即使C#和VB.NET本身禁止它?)

  3. 甚至可以 (在C#或VB.NET中)将Nullable作为object包装?

有两件事情在发生:

1)编译器将“null”视为空引用而不是空 …它需要转换为的任何类型的null值。 在Nullable的情况下,它只是HasValue字段/属性的值为False。 那么如果你有一个int?类型的变量int? ,这个变量的值很可能是null – 你只需要改变你对null含义的理解。

2)拳击可空类型由CLR本身进行特殊处理。 这与您的第二个示例相关:

  int? i = new int?(); object x = i; 

编译器会将任何可空类型值与不可空类型值区别开来。 如果该值不为null,则结果将与装入与非可空类型值相同的值相同 – 所以int? 值为5的方式与值为5的int相同 – “可空性”丢失。 但是,可空类型的空值仅被装箱为空引用,而不是根本创建对象。

这是在社区的要求下在CLR v2周期的后期引入的。

这意味着没有“盒装可空值类型值”这样的东西。

你做对了: Nullable在VB和C#中都得到了编译器的特殊处理。 因此:

  1. 是。 语言编译器需要特殊情况Nullable
  2. 编译器重构Nullable 。 操作符只是语法糖。
  3. 从来没听说过。

我问自己同样的问题,我也期望在.net Nullable源代码中有一些Nullable隐式运算符,所以我看看对应于int? a = null;的IL代码是什么int? a = null; int? a = null; 了解幕后发生的事情:

c#代码:

 int? a = null; int? a2 = new int?(); object a3 = null; int? b = 5; int? b2 = new int?(5); 

IL代码(使用LINQPad 5生成):

 IL_0000: nop IL_0001: ldloca.s 00 // a IL_0003: initobj System.Nullable IL_0009: ldloca.s 01 // a2 IL_000B: initobj System.Nullable IL_0011: ldnull IL_0012: stloc.2 // a3 IL_0013: ldloca.s 03 // b IL_0015: ldc.i4.5 IL_0016: call System.Nullable..ctor IL_001B: ldloca.s 04 // b2 IL_001D: ldc.i4.5 IL_001E: call System.Nullable..ctor IL_0023: ret 

我们看到编译器改变了int? a = null int? a = null到类似int? a = new int?()东西int? a = new int?() int? a = new int?()object a3 = null完全不同。 所以很明显Nullables有一个特殊的编译器处理。