Nullable 的装箱/拆箱行为怎么可能?
我今天早些时候发生的事情让我摸不着头脑。
任何Nullable
类型的变量都可以赋值为null
。 例如:
int? i = null;
起初我无法看到如果不以某种方式定义从object
到Nullable
的隐式转换,这将是如何可能的:
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.
此外,如果HasValue
为true
, 那么该值将被设置为T
而不是T?
:
int? i = 5; object x = i; // Now x is a boxed int, NOT a boxed Nullable.
但这似乎意味着存在从Nullable
到object
的自定义隐式转换:
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
类型进行了特殊处理。
-
从理论上讲,在
Nullable
没有得到这种特殊处理的情况下,可能存在不同的基于CLI的语言吗? 因此,Nullable
类型不会在不同语言中表现出不同的行为吗? -
C#和VB.NET如何实现这种行为? 它是否受到CLR的支持? (也就是说,CLR是否允许类型以某种方式“覆盖”它的装箱方式,即使C#和VB.NET本身禁止它?)
-
甚至可以 (在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#中都得到了编译器的特殊处理。 因此:
- 是。 语言编译器需要特殊情况
Nullable
。 - 编译器重构
Nullable
。 操作符只是语法糖。 - 从来没听说过。
我问自己同样的问题,我也期望在.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有一个特殊的编译器处理。