2012-01-08 26 views
8

後來我編譯了兩個版本的代碼,一個使用(Nullable<T>)x.GetValueOrDefault(y),另一個使用(Nullable<T>)x ?? y)空合併操作符含義?

在反編譯爲IL後,我注意到空合併運算符被轉換爲GetValueOrDefault調用。

由於這是一個方法調用,可以傳遞一個表達式,該表達式在執行該方法之前被評估,因此似乎總是執行y

例如:

using System; 

public static class TestClass 
{ 
    private class SomeDisposable : IDisposable 
    { 
     public SomeDisposable() 
     { 
      // Allocate some native resources 
     } 

     private void finalize() 
     { 
      // Free those resources 
     } 

     ~SomeDisposable() 
     { 
      finalize(); 
     } 

     public void Dispose() 
     { 
      finalize(); 
      GC.SuppressFinalize(this); 
     } 
    } 

    private struct TestStruct 
    { 
     public readonly SomeDisposable _someDisposable; 
     private readonly int _weirdNumber; 

     public TestStruct(int weirdNumber) 
     { 
      _weirdNumber = weirdNumber; 
      _someDisposable = new SomeDisposable(); 
     } 
    } 

    public static void Main() 
    { 
     TestStruct? local = new TestStruct(0); 

     TestStruct local2 = local ?? new TestStruct(1); 

     local2._someDisposable.Dispose(); 
    } 
} 

似乎導致成不適對象,並可能影響性能了。

首先,這是真的嗎?或者JIT或類似的東西改變了實際執行的ASM代碼?

其次,有人可以解釋爲什麼它有這種行爲?

NOTE:這只是一個例子,它不是基於真實的代碼,並且請不要發表評論,如「這是錯誤的代碼」。

IL DASM:
好的,當我.Net框架2.0編譯此它導致相同碼與呼叫空聚結和GetValueOrDefault。使用.NET Framework 4.0中,它會產生這兩個代碼:

GetValueOrDefault:

.method private hidebysig static void Main() cil managed 
{ 
    .entrypoint 
    // Code size  19 (0x13) 
    .maxstack 2 
    .locals init ([0] valuetype [mscorlib]System.Nullable`1<int32> nullableInt, 
      [1] int32 nonNullableInt) 
    IL_0000: nop 
    IL_0001: ldloca.s nullableInt 
    IL_0003: initobj valuetype [mscorlib]System.Nullable`1<int32> 
    IL_0009: ldloca.s nullableInt 
    IL_000b: ldc.i4.1 
    IL_000c: call  instance !0 valuetype [mscorlib]System.Nullable`1<int32>::GetValueOrDefault(!0) 
    IL_0011: stloc.1 
    IL_0012: ret 
} // end of method Program::Main 

空凝聚:

.method private hidebysig static void Main() cil managed 
{ 
    .entrypoint 
    // Code size  32 (0x20) 
    .maxstack 2 
    .locals init (valuetype [mscorlib]System.Nullable`1<int32> V_0, 
      int32 V_1, 
      valuetype [mscorlib]System.Nullable`1<int32> V_2) 
    IL_0000: nop 
    IL_0001: ldloca.s V_0 
    IL_0003: initobj valuetype [mscorlib]System.Nullable`1<int32> 
    IL_0009: ldloc.0 
    IL_000a: stloc.2 
    IL_000b: ldloca.s V_2 
    IL_000d: call  instance bool valuetype [mscorlib]System.Nullable`1<int32>::get_HasValue() 
    IL_0012: brtrue.s IL_0017 
    IL_0014: ldc.i4.1 
    IL_0015: br.s  IL_001e 
    IL_0017: ldloca.s V_2 
    IL_0019: call  instance !0 valuetype [mscorlib]System.Nullable`1<int32>::GetValueOrDefault() 
    IL_001e: stloc.1 
    IL_001f: ret 
} // end of method Program::Main 

事實證明,這是不再的情況下並且當HasValue返回false時,它完全跳過GetValueOrDefault呼叫。

+0

如果'y'是一個方法的調用,導致需要處理的對象,那麼在任何情況下都有泄漏,即'x'爲空。 – 2012-01-08 19:46:47

+0

@ M.Babcock不是真的泄漏,只是推遲清理內存。看看修改後的例子,我希望這能更好地解釋這個問題。 – Aidiakapi 2012-01-08 20:07:00

+0

其實我說錯了,如果'local'不是null並且實際調用了方法,那麼你將會泄漏,因爲結果將從GC中收集而不被處置。要回答每次是否實際調用方法的問題,可以在方法中放置一個斷點並運行它。 – 2012-01-08 20:17:27

回答

6

反編譯後,我注意到,空合併運算符轉換爲GetValueOrDefault調用。

x ?? y被轉換成x.HasValue ? x.GetValueOrDefault() : y。它不會被轉換爲x.GetValueOrDefault(y),如果是的話,它會是一個編譯器錯誤。你是對的,y不應該評估,如果x不爲空,它不是。

編輯:如果y的評價,可以無副作用證明(這裏的「副作用」包括「拋出一個異常」),然後轉化爲x.GetValueOrDefault(y)不一定是錯的,但它仍然是一個我認爲編譯器不會執行的轉換:並不是所有這些優化都會有用的情況。

+0

我很久以前使用.Net Framework 2.0編譯器執行了這個測試,我也做了一些更多的資源,並且從MS提供的參考代碼很明顯,'GetValueOrDefault'直接返回內部值,而'get_Value'執行一個首先檢查,這很可能是爲什麼它選擇'GetValueOrDefault'而不是'get_Value'。謝謝:) – Aidiakapi 2012-01-08 20:38:42

+0

是的,'Value'屬性getter基本上是'if(!HasValue)throw;返回GetValueOrDefault();' - 所以如果你已經確定了可爲空的對象是否有值,就沒什麼意義了:) – hvd 2012-01-08 21:09:29

+0

或者確切地說'if(!HasValue)throw'...';返回值;'。雖然'GetValueOrDefault()'是'返回值;'。非常有趣的是,由於(大多數)結構的不變性,它是有效的,它只需要重新創建'T可以爲空'的包裝器,所以沒有太多用處來檢查'GetValueOrDefault()'中的HasValue,因爲結構自動啓動out默認爲0位。 – Aidiakapi 2012-01-08 22:25:20