可空類型為什么可以為空?也許會被面試到哦。。。


 

  也許某天你來某一家公司面試,或許就會被問到這個問題,當你看到這個問題,也許會立即反編譯下源代碼看個究竟。

  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 Methods
100  

 

當你reflector之后,你可能會快速的認為這個就是答案,但是你真的把這個代碼拷貝到編輯器中,你會發現如下的錯誤。

 

從圖中可以看到,原來事情沒有這么簡單,最后還是回到了原來的問題上,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放入到線程棧中,不知道編譯器在initobj中

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

 

最后要說的是:希望大家討論討論,畢竟我也是猜測而已,並沒有實實在在的看到那些給我們隱藏的東西。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM