代码之家  ›  专栏  ›  技术社区  ›  Dan Tao

Nullable<T>的装箱/拆箱行为如何可能?

  •  31
  • Dan Tao  · 技术社区  · 14 年前

    今天早些时候我突然想到一件让我抓狂的事。

    Nullable<T> 可分配给 null . 例如:

    int? i = null;
    

    一开始,如果不定义从 object 可为空<T> :

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

    但是上面的操作符显然不存在,如果它确实存在,那么下面的操作符也必须是合法的,至少在编译时是合法的(它不是):

    int? i = new object();
    

    然后我意识到也许 可为空<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 可为空<T> 值,则该值将被装箱为 无效的 :

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

    true , 然后 T 而不是一个 T?

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

    但是 似乎意味着有一个自定义隐式转换从 对象

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

    显然情况并非如此 对象 是所有类型的基类,用户定义的与基类之间的隐式转换是非法的(也应该是非法的)。

    看来 object x = i; 应该是盒子 i 和其他任何值类型一样 x.GetType() typeof(int?) NullReferenceException ).

    所以我仔细研究了一下,果然,这种行为是特定于 可为空<T> 类型,在C#和VB.NET版规范,不可在任何用户定义的 struct (C)或 Structure (VB.NET版).

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

    这种特殊的装箱和拆箱行为似乎不可能用手实现。它只起作用,因为C#和VB.NET版特别对待 可为空<T> 类型。

    1. 可为空<T> 可为空<T> 不同的行为 用不同的语言?

    2. 实现 这种行为?CLR是否支持它?(也就是说,CLR是否允许类型以某种方式“重写”装箱的方式,即使C#和VB.NET版他们自己禁止吗?)

    3. 是平的吗 (以C#或VB.NET版)至方框a 可为空<T> 作为 ?

    3 回复  |  直到 14 年前
        1
  •  42
  •   Jon Skeet    14 年前

    发生了两件事:

    1) 编译器将“null”不视为null 参考 价值 ... 它需要转换为的任何类型的空值。如果是 Nullable<T> 它的值对于 HasValue 字段/属性。如果你有一个 int? ,这个变量的值很可能是 null 无效的 意味着一点点。

    2) 装箱可为null的类型得到CLR本身的特殊处理。这与您的第二个示例有关:

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

    编译器将以与不可为null的类型值不同的方式对任何可为null的类型值进行装箱。如果值不为null,则结果将与将相同的值装箱为不可为null的类型值相同-因此 内景? 值为5时,以与 int 值为5时,“可空性”将丢失。但是,可为null类型的null值仅限于null引用,而不是创建对象。

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

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

        2
  •  2
  •   Konrad Rudolph    14 年前

    你说得对: Nullable<T>

    1. 对。语言编译器需要特殊情况
    2. 编译器重构 . 运算符只是语法糖。
    3. 据我所知不是。
        3
  •  0
  •   asidis    7 年前

    我也在问自己同样的问题,我也希望有一些隐式运算符 Nullable<T> .net Nullable source code 所以我看了一下IL代码对应的是什么 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<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