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

一線碼農發表於2014-07-09

 

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

  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中

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

 

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

 

相關文章