关于可空类型,如果我们手动显式模仿,会编译出错:
从图中可以看到,原来事情没有这么简单,最后还是回到了原来的问题上,null不能给值类型赋值,这个时候,你可能就比较好奇。
我们的FCL中定义的类怎么就能逃过编译器呢?
①:我们用ILdasm看下il代码。
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 Nullable<Int32> i = null; 6 } 7 }
②:下面我们再将Nullable<Int32> i = null 改成 Nullable<Int32> i = 0,看看il代码是怎么样的。
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 Nullable<Int32> i = 0; 6 } 7 }
下面我们比较比较这两张图不一样的地方。
《1》 当 Nullable<Int32> i = 0 的时候,发现Nullable被实例化了(instance),并且还调用了其构造函数(ctor(!0)),
这种情况我们看Nullable的结构体定义,发现是非常合乎情理的。
《2》当 Nullable<Int32> i = null 的时候,从IL代码上看,只是调用了initobj指令,并没有实例化,也没有调用构造函数,
再看看这个指令的意思:将位于指定地址的对象的所有字段初始化为空引用或适当的基元类型的 0。
①:既然是”初始化“操作,那我应该也可以写成这样:
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 Nullable<Int32> i = new Nullable<Int32>(); 6 } 7 }
②:既然是“初始化”,那么作为null的Nullable应该可以调用实例方法并不报错,这就如指令说的一样,如果成功,那就
说明null只是Nullable的一种状态,不能跟“类”中的空引用混淆。
从上面的三张图上可以看出,也许答案就在这个里面,编译器和CLR作为“特等公民”在底层做了很多我们看不到的东西,
这其中就像上图一样给我们多加了一种”可空状态“,只是如何做的,我们看不到而已。
《3》既然说到null,我也很好奇的看看到底“类”下面的null是什么情况。
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 Program p = null; 6 } 7 }
ldnull的意思是:将空引用推送到计算堆栈上。
可以看到,既然没有new,也就不会在堆中分配内存,而这里是将null放入到线程栈中
标签:string,Nullable,void,Program,static,可空,类型,null From: https://www.cnblogs.com/wwkk/p/16817710.html