38

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

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

int? i = null;

起初,如果不以某种方式定义从objectto的隐式转换,我看不到这怎么可能Nullable<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.

此外,如果HasValueis true该值将被装箱为 aT而不是 a T?

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

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

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

这显然不是object所有类型的基类,用户定义的与基类型之间的隐式转换是非法的(它们也应该如此)。

似乎object x = i;应该像任何其他值类型一样装箱i,这样x.GetType()会产生与typeof(int?)(而不是 throw a 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 中)将 a 装箱Nullable<T>object

4

3 回答 3

47

有两件事发生:

1)编译器不将“null”视为空引用,而是将其视为空......它需要转换为的任何类型的空值。在 a 的情况下,Nullable<T>它只是HasValue字段/属性为 False 的值。因此,如果您有一个 type 变量int?,那么该变量的值很可能是null- 您只需要null稍微改变您对什么意思的理解。

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

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

编译器会将任何可空类型值与不可空类型值不同地装箱。如果该值不为空,则结果将与将相同的值装箱为不可为空的类型值相同-因此int?值为 5 的装箱方式与值为 5 的装箱方式相同int-“可空性”丢失. 但是,可空类型的空值被装箱到空引用,而不是创建一个对象。

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

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

于 2010-09-23T05:28:56.567 回答
2

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

  1. 是的。语言编译器需要特殊情况Nullable<T>
  2. 编译器重构了Nullable<T>. 运算符只是语法糖。
  3. 从来没听说过。
于 2010-09-23T05:15:12.163 回答
2

我问自己同样的问题,我也期待Nullable<T>.net Nullable 源代码中有一些隐式运算符,所以我查看了对应的 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 = nullint? a = new int?()object a3 = null. 所以很明显 Nullables 有一个特殊的编译器处理。

于 2018-05-09T13:33:37.420 回答