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

时间:2010-09-23 05:12:13

标签: c# .net vb.net nullable boxing

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

Nullable<T>类型的任何变量都可以分配给null。例如:

int? i = null;

首先,如果没有以某种方式定义从objectNullable<T>的隐式转换,我无法看到这是如何实现的:

public static implicit operator Nullable<T>(object box);

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

int? i = new object();

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

public abstract class DummyBox
{
    private DummyBox()
    { }
}

public struct Nullable<T> where T : struct
{
    public static implicit operator Nullable<T>(DummyBox box)
    {
        if (box == null)
        {
            return new Nullable<T>();
        }

        // This should never be possible, as a DummyBox cannot be instantiated.
        throw new InvalidCastException();
    }
}

但是,这并不能解释我接下来会发生什么:如果任何HasValue值的false属性为Nullable<T>,那么该值将被设置为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<int>.

似乎意味着存在从Nullable<T>object的自定义隐式转换:

public static implicit operator object(Nullable<T> value);

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

似乎object x = i;应该像其他任何值类型一样i,因此x.GetType()会产生与typeof(int?)相同的结果(而不是抛出NullReferenceException }})。

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

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

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

  1. 从理论上讲,在Nullable<T>没有得到这种特殊处理的情况下,是否存在不同的基于CLI的语言?因此Nullable<T>类型不会在不同语言中展示不同的行为吗?

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

  3. 甚至可能(在C#或VB.NET中)将Nullable<T>打包为object

3 个答案:

答案 0 :(得分:33)

有两件事情发生了:

1)编译器将“null”视为null 引用,但将null 视为需要转换为的任何类型的null值。在Nullable<T>的情况下,它只是HasValue字段/属性的值为False。因此,如果您有int?类型的变量,则该变量的值很可能为null - 您只需要更改对null的含义的理解。

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

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

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

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

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

答案 1 :(得分:2)

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

  1. 是。语言编译器需要特殊情况Nullable<T>
  2. 编译器重构Nullable<T>的使用。操作符只是语法糖。
  3. 不是我知道的。

答案 2 :(得分:0)

我问自己同样的问题,我也期望在.net Nullable source code中为Nullable<T>提供一些隐式运算符,所以我看一下与int? a = null;对应的IL代码是什么现场背后发生了什么:

c#c​​ode:

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<System.Int32>
IL_0009:  ldloca.s    01 // a2
IL_000B:  initobj     System.Nullable<System.Int32>
IL_0011:  ldnull      
IL_0012:  stloc.2     // a3
IL_0013:  ldloca.s    03 // b
IL_0015:  ldc.i4.5    
IL_0016:  call        System.Nullable<System.Int32>..ctor
IL_001B:  ldloca.s    04 // b2
IL_001D:  ldc.i4.5    
IL_001E:  call        System.Nullable<System.Int32>..ctor
IL_0023:  ret   

我们看到编译器将int? a = null更改为int? a = new int?(),与object a3 = null完全不同。所以Nullables有一个特殊的编译器处理。