2009-08-25 40 views
3

我對下面的代碼一些問題:什麼時候使用語句框的參數,當它是一個結構?

using System; 

namespace ConsoleApplication2 
{ 
    public struct Disposable : IDisposable 
    { 
     public void Dispose() { } 
    } 

    class Program 
    { 
     static void Main(string[] args) 
     { 
      using (Test()) { } 
     } 

     static Disposable Test() 
     { 
      return new Disposable(); 
     } 
    } 
} 

我的問題是:

  • 請問使用語句,關於Disposable結構工作時,Test()箱的結構,否則不予退換?
  • 我該如何找到答案?

要嘗試找出我自己,我檢查了IL通過上面的代碼產生的,這裏的IL爲Main(...)方法:

.method private hidebysig static void Main(string[] args) cil managed 
{ 
    .entrypoint 
    .maxstack 1 
    .locals init (
     [0] valuetype ConsoleApplication2.Disposable CS$3$0000) 
    L_0000: call valuetype ConsoleApplication2.Disposable ConsoleApplication2.Program::Test() 
    L_0005: stloc.0 
    L_0006: leave.s L_0016 
    L_0008: ldloca.s CS$3$0000 
    L_000a: constrained ConsoleApplication2.Disposable 
    L_0010: callvirt instance void [mscorlib]System.IDisposable::Dispose() 
    L_0015: endfinally 
    L_0016: ret 
    .try L_0006 to L_0008 finally handler L_0008 to L_0016 
} 

我懷疑調用虛方法那裏, L_0010將引入拳擊操作,但實際box指令不在這裏。

我之所以問的原因是,前一段時間,可能是1 - 2年,我在網上看到有人對使用語句進行了「優化」評論。在這種情況下,using語句被用作對象的短時間鎖定的語法,其中在該方法中獲取了鎖,並且返回了一個結構體,在處理該結構體時,會釋放鎖,像這樣的代碼:

using (LockTheObject()) 
{ 
    // use the object 
} 

和意見是,由IDisposable改變LockTheObject方法的返回類型實際使用的結構,避免了拳擊。

但我想知道這是真的還是真的。

任何人都可以指向正確的方向嗎?如果爲了看到盒子的操作,我將不得不檢查運行時的彙編代碼,請給我看一個什麼樣的例子,我很熟悉彙編代碼,所以這不是問題,但沒有跳出來在我看着那個時候。

回答

6

看起來好像是放入using語句中的任何值類型都不會被裝箱。這似乎是一種C#優化,因爲只有在實現IDisposable的值類型處於using語句中時,纔會忽略裝箱,而不是在任何其他上下文中。

欲瞭解更多信息,請參閱The Using Statement And Disposable Value Types

前一段伊恩·格里菲思寫了一篇關於 的改進,他的TimedLock類 中,他改變了它從類 一個結構。此更改導致 值類型實現了 IDisposable。在我腦海裏的 我有一個嘮叨的問題,我很快就忘了。在調用Dispose時, 問題不會包含那個 類型的實例嗎?

而且還Oh No! Not the TimedLock Again!

約翰·金沙指出在 代碼,我在最近的博客顯示了使用上的鎖超時 沒有 放棄最 丙的#lock關鍵字的便利性缺陷。

+0

約束和callvirt之間的相互作用是我一直在尋找,謝謝! –

3

值類型的實例方法使用this參數作爲它們的第一個參數,類似於引用類型上的實例方法。但是,這種情況下的參數是指向對象數據的託管指針,而不是對盒裝對象的引用。您可能會發現它在內存佈局是這樣的:

Unboxed object: 
----------------------------------------- 
|    DATA      | 
----------------------------------------- 
^ managed pointer to struct 

Boxed object: 
------------------------------------------------------------ 
| GC/Object header |    [Boxed] DATA    | 
------------------------------------------------------------ 
        ^The 'unbox' opcode gives a managed pointer to the boxed data 
^ A *reference* to any instance of a reference type or boxed object, points here 

DATA是在這兩個cases¹相同。

值類型的實例方法期望託管的指針指向數據特別是因此不需要裝箱對象。如您所見,在調用之前使用constrained操作碼。它告訴運行時,以下callvirt指令正在接收指向ConsoleApplication2.Disposable結構的託管指針,而不是它通常接收的對象引用。這樣做,JIT可以解決由結構實現的密封過載Dispose(),並直接調用它而不用裝箱對象。如果沒有constrained前綴,則傳遞給callvirt指令的對象必須是對象引用,因爲標準虛擬調用動態解析過程基於以下事實:GC/Object頭在預期位置始終爲總是 - 是,這將強制價值類型的裝箱。

¹現在我們繼續前進並忽略Nullable<T>

4

這是If my struct implements IDisposable will it be boxed when used in a using statement?

更新的副本:這個問題問得the subject of my blog in March of 2011。感謝您的好問題!

Andrew Hare的回答是正確的;我只是想添加一個有趣的額外說明。我們發出的優化 - 使用約束callvirt在可能時跳過拳擊 - 實際上嚴格地說是違反了C#規範。該規範指出,我們爲一個值類型資源生成的finally塊是:

 finally 
    { 
     ((IDisposable)resource).Dispose(); 
    } 

這明顯是對值類型的裝箱轉換。有可能構建出實施過程中缺乏拳擊的人爲情景。

(感謝是由於弗拉基米爾尼科夫這個規範違反指出了我。)

+0

我有時會惋惜缺少任何語法來指定「沒有裝箱的重新解釋」類型轉換。因爲'resource'必須是一個滿足'IDisposable'約束的類型,所以我不知道規範是否可以說這個行爲等同於調用'static void DisposeIt (ref T it)where IDisposable {it。 Dispose();}',不同之處在於C#編譯器可能會在線生成代碼[即使C#編譯器沒有內聯它,看起來JITter可能]。 – supercat

+0

嗨,Eric,我想知道受約束的callvirt是否仍然違反了C#規範。 – codingadventures

相關問題