今天早些时候我刚刚发生的事情让我抓耳挠腮。
任何Nullable类型的变量都可以分配给null。例如:
起初,我不知道如果不以某种方式定义从object到Nullable的隐式转换,这是怎么可能的:
1
| public static implicit operator Nullable<T>(object box); |
但是上面的操作符显然不存在,就好像它存在一样,那么下面的操作符也必须是合法的,至少在编译时是合法的(它不是合法的):
然后我意识到,也许Nullable类型可以定义一个隐式转换,转换为一些永远无法实例化的任意引用类型,如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| 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值,那么该值将被装箱为null:
1 2
| int? i = new int?();
object x = i ; // Now x is null. |
此外,如果HasValue是true,则该值将作为T而不是T?装箱:
1 2
| int? i = 5;
object x = i; // Now x is a boxed int, NOT a boxed Nullable<int>. |
但这似乎意味着有一种从Nullable到object的隐式转换:
1
| public static implicit operator object(Nullable<T> value); |
显然情况并非如此,因为object是所有类型的基类,用户定义的隐式转换到/从基类型转换是非法的(以及它们应该是非法的)。
似乎object x = i;应该像其他值类型一样框入i,这样x.GetType()会产生与typeof(int?)相同的结果(而不是抛出NullReferenceException)。
所以我仔细研究了一下,可以肯定的是,这个行为是针对Nullable类型的,在c和vb.net规范中都有专门的定义,在任何用户定义的struct或Structure中都不可复制。
这就是为什么我仍然困惑的原因。
这种特殊的装箱和拆箱行为似乎不可能用手工实现。它只起作用,因为C和vb.net都对Nullable类型进行了特殊处理。
理论上不可能存在一种不同的基于cli的语言,而Nullable没有得到这种特殊的处理吗?因此,Nullable类型在不同的语言中不会表现出不同的行为吗?
C和vb.net如何实现这种行为?CLR支持它吗?(也就是说,clr是否允许类型以某种方式"重写"装箱的方式,即使c和vb.net本身禁止这样做?)
是否有可能(在c或vb.net中)将Nullable装箱为object?
- 实现该行为的是JIT编译器。更多信息请访问:stackoverflow.com/questions/1583050/&hellip;
- 我意识到我迟到了7年。但是,我想建议像我这样好奇的人也阅读参考资料以获得更多的见解。referencesource.microsoft.com/mscorlib/system/&hellip;
有两件事发生了:
1)编译器不将"空"视为空引用,而是将其视为空值…需要转换为的任何类型的空值。在Nullable的情况下,它只是HasValue字段/属性的值为假。所以如果你有一个int?类型的变量,这个变量的值很可能是null,你只需要改变你对null的理解一点。
2)装箱可空类型由clr本身得到特殊处理。这与您的第二个示例相关:
1 2
| int? i = new int?();
object x = i ; |
编译器将以不同于不可为空的类型值的方式对任何可为空的类型值进行装箱。如果值不为空,则结果将与将值与不可为空的类型值装箱相同-因此,值为5的int?与值为5的int装箱的方式相同-"可空性"将丢失。但是,可以为空的类型的空值仅限于空引用,而不是创建对象。
这是在clr v2周期的后期,应社区的请求而引入的。
它意味着没有"装箱的可空值类型值"这样的东西。
- 像往常一样,在IL代码上,对象x=i被转换成box指令,指示在clr级别上的支持。
- 我明白你所说的从clr得到特殊治疗的拳击是什么意思了:我刚写了一个快速测试,看了Reflector中的IL,发现C编译器似乎没有做任何特殊的事情来移除Nullable本身的拳击。但是,在C和vb.net规范中单独规定了Nullable的拳击效果,这难道不奇怪吗?它也在cli规范中吗,你知道吗?
- @丹:是的,它在C规范中。我不知道它是否在vb.net规范中。在cli规范(ecma-335)中,它在分区1的第8.2.4节中。
- 我也能在vb规范中找到它(在第8.6.1节中,至少对于该规范的9.0版)。我想我只是不明白为什么要在三个地方指定它——cli规范,以及c和vb规范——而不仅仅是在cli规范中。
你说得对:Nullable在vb和c中都得到了编译器的特殊处理。因此:
对。语言编译器需要特殊情况Nullable。
编译器重构Nullable的用法。算符只是句法上的糖分。
我不知道。
- 因此,作为对第二个答案的回应:C编译器(例如)必须将object x = i;重构为类似object x = i.HasValue ? (object)i.Value : null;的东西,对吗?这意味着语言实际上完全不允许标准的拳击行为(我刚刚意识到object x = (int?)5;使x成为盒装int,而不是Nullable。有趣的…
- 在IL中,clr对nullable有特殊的支持。编译器发出box指令,clr检查值以决定是将ref设置为空值还是box实际值类型值。
- 从我刚刚在Reflector中发现的GetRandomNullable方法生成的IL来看,似乎Vinayc是对的:我看不到C编译器实际上在重构装箱,这意味着(对我而言)特殊处理实际上在CLR级别上发生。但如果这是真的,那么(在我看来)这种行为将被定义在语言规范本身中似乎很奇怪。有什么想法吗?
- @丹涛:对拳击来说,这是真的。我的答案只包括操作员"过载"(== null测试、操作员提升等)。
我问自己同样的问题,我也希望在.NET可空源代码中有一些用于Nullable的隐式运算符,所以我查看了与int? a = null;对应的IL代码是什么,以了解在场景后面发生了什么:
C代码:
1 2 3 4 5
| int? a = null;
int? a2 = new int?();
object a3 = null;
int? b = 5;
int? b2 = new int?(5); |
IL代码(用LinqPad 5生成):
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| 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非常不同。所以很明显,空值具有特殊的编译器处理。