为了账号安全,请及时绑定邮箱和手机立即绑定

可空类型为什么可以为空?

标签:
资讯

 

  也许某天你来某一家公司面试,或许就会被问到这个问题,当你看到这个问题,也许会立即反编译下源代码看个究竟。

复制代码

  1 [Serializable, StructLayout(LayoutKind.Sequential), __DynamicallyInvokable]  2 public struct Nullable<T> where T: struct  3 {  4     private bool hasValue;  5     internal T value;  6     [TargetedPatchingOptOut("Performance critical to inline across NGen image boundaries"), __DynamicallyInvokable]  7     public Nullable(T value)  8     {  9         this.value = value; 10         this.hasValue = true; 11     } 12  13     [__DynamicallyInvokable] 14     public bool HasValue 15     { 16         [__DynamicallyInvokable, TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")] 17         get 18         { 19             return this.hasValue; 20         } 21     } 22     [__DynamicallyInvokable] 23     public T Value 24     { 25         [TargetedPatchingOptOut("Performance critical to inline across NGen image boundaries"), __DynamicallyInvokable] 26         get 27         { 28             if (!this.HasValue) 29             { 30                 ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_NoValue); 31             } 32             return this.value; 33         } 34     } 35     [TargetedPatchingOptOut("Performance critical to inline across NGen image boundaries"), __DynamicallyInvokable] 36     public T GetValueOrDefault() 37     { 38         return this.value; 39     } 40  41     [__DynamicallyInvokable] 42     public T GetValueOrDefault(T defaultValue) 43     { 44         if (!this.HasValue) 45         { 46             return defaultValue; 47         } 48         return this.value; 49     } 50  51     [__DynamicallyInvokable] 52     public override bool Equals(object other) 53     { 54         if (!this.HasValue) 55         { 56             return (other == null); 57         } 58         if (other == null) 59         { 60             return false; 61         } 62         return this.value.Equals(other); 63     } 64  65     [__DynamicallyInvokable] 66     public override int GetHashCode() 67     { 68         if (!this.HasValue) 69         { 70             return 0; 71         } 72         return this.value.GetHashCode(); 73     } 74  75     [__DynamicallyInvokable] 76     public override string ToString() 77     { 78         if (!this.HasValue) 79         { 80             return ""; 81         } 82         return this.value.ToString(); 83     } 84  85     [__DynamicallyInvokable] 86     public static implicit operator T?(T value) 87     { 88         return new T?(value); 89     } 90  91     [__DynamicallyInvokable] 92     public static explicit operator T(T? value) 93     { 94         return value.Value; 95     } 96 } 97  98   99 Collapse Methods100

复制代码

 

当你reflector之后,你可能会快速的认为这个就是答案,但是你真的把这个代码拷贝到编辑器中,你会发现如下的错误。

 

从图中可以看到,原来事情没有这么简单,最后还是回到了原来的问题上,null不能给值类型赋值,这个时候,你可能就比较好奇。

我们的FCL中定义的类怎么就能逃过编译器呢?

 

①:我们用ILdasm看下il代码。

复制代码

1     class Program2     {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 Program2     {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 Program2     {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 Program2     {3         static void Main(string[] args)4         {5             Program p = null;6         }7     }

复制代码

 

ldnull的意思是:将空引用推送到计算堆栈上。

可以看到,既然没有new,也就不会在堆中分配内存,而这里是将null放入到线程栈中,不知道编译器在initobj中

是否也有类似的操作。。。

 

最后要说的是:希望大家讨论讨论,毕竟我也是猜测而已,并没有实实在在的看到那些给我们隐藏的东西。

 

点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消