手记

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

 

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

  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中

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

 

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

 

0人推荐
随时随地看视频
慕课网APP