[Serializable, StructLayout(LayoutKind.Sequential), __DynamicallyInvokable] public struct Nullable<T> where T: struct { private bool hasValue; internal T value; [TargetedPatchingOptOut("Performance critical to inline across NGen image boundaries"), __DynamicallyInvokable] public Nullable(T value) { this.value = value; this.hasValue = true; } [__DynamicallyInvokable] public bool HasValue { [__DynamicallyInvokable, TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")] get { return this.hasValue; } } [__DynamicallyInvokable] public T Value { [TargetedPatchingOptOut("Performance critical to inline across NGen image boundaries"), __DynamicallyInvokable] get { if (!this.HasValue) { ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_NoValue); } return this.value; } } [TargetedPatchingOptOut("Performance critical to inline across NGen image boundaries"), __DynamicallyInvokable] public T GetValueOrDefault() { return this.value; } [__DynamicallyInvokable] public T GetValueOrDefault(T defaultValue) { if (!this.HasValue) { return defaultValue; } return this.value; } [__DynamicallyInvokable] public override bool Equals(object other) { if (!this.HasValue) { return (other == null); } if (other == null) { return false; } return this.value.Equals(other); } [__DynamicallyInvokable] public override int GetHashCode() { if (!this.HasValue) { return 0; } return this.value.GetHashCode(); } [__DynamicallyInvokable] public override string ToString() { if (!this.HasValue) { return ""; } return this.value.ToString(); } [__DynamicallyInvokable] public static implicit operator T?(T value) { return new T?(value); } [__DynamicallyInvokable] public static explicit operator T(T? value) { return value.Value; } } Collapse Methods
當你reflector之後,你可能會快速的認爲這個就是答案,但是你真的把這個代碼拷貝到編輯器中,你會發現如下的錯誤。
從圖中可以看到,原來事情沒有這麼簡單,最後還是回到了原來的問題上,null不能給值類型賦值,這個時候,你可能就比較好奇。
我們的FCL中定義的類怎麼就能逃過編譯器呢?
①:我們用ILdasm看下il代碼。
class Program { static void Main(string[] args) { Nullable<Int32> i = null; } }
②:下面我們再將Nullable<Int32> i = null 改成 Nullable<Int32> i = 0,看看il代碼是怎麼樣的。
class Program { static void Main(string[] args) { Nullable<Int32> i = 0; } }
下面我們比較比較這兩張圖不一樣的地方。
《1》 當 Nullable<Int32> i = 0 的時候,發現Nullable被實例化了(instance),並且還調用了其構造函數(ctor(!0)),
這種情況我們看Nullable的結構體定義,發現是非常合乎情理的。
《2》當 Nullable<Int32> i = null 的時候,從IL代碼上看,只是調用了initobj指令,並沒有實例化,也沒有調用構造函數,
再看看這個指令的意思:將位於指定地址的對象的所有字段初始化爲空引用或適當的基元類型的 0。
①:既然是」初始化「操作,那我應該也可以寫成這樣:
class Program { static void Main(string[] args) { Nullable<Int32> i = new Nullable<Int32>(); } }
②:既然是「初始化」,那麼作爲null的Nullable應該可以調用實例方法並不報錯,這就如指令說的一樣,如果成功,那就
說明null只是Nullable的一種狀態,不能跟「類」中的空引用混淆。
從上面的三張圖上可以看出,也許答案就在這個裏面,編譯器和CLR作爲「特等公民」在底層做了很多我們看不到的東西,
這其中就像上圖一樣給我們多加了一種」可空狀態「,只是如何做的,我們看不到而已。
《3》既然說到null,我也很好奇的看看到底「類」下面的null是什麼情況。
class Program { static void Main(string[] args) { Program p = null; } }
ldnull的意思是:將空引用推送到計算堆棧上。
可以看到,既然沒有new,也就不會在堆中分配內存,而這裏是將null放入到線程棧中,不知道編譯器在initobj中
是否也有類似的操作。。。
最後要說的是:希望大家討論討論,畢竟我也是猜測而已,並沒有實實在在的看到那些給我們隱藏的東西。