c# - collection - using idisposable




正確使用IDisposable接口 (12)

通過閱讀MSDN文檔 ,我知道IDisposable接口的“主要”用途是清理非託管資源。

對我而言,“非託管”意味著像數據庫連接,套接字,窗口句柄等。但是,我已經看到代碼實現了Dispose()方法來釋放託管資源,這對我來說似乎是多餘的,因為垃圾收集器應該照顧你的。

例如:

public class MyCollection : IDisposable
{
    private List<String> _theList = new List<String>();
    private Dictionary<String, Point> _theDict = new Dictionary<String, Point>();

    // Die, clear it up! (free unmanaged resources)
    public void Dispose()
    {
        _theList.clear();
        _theDict.clear();
        _theList = null;
        _theDict = null;
    }

我的問題是,這是否使得MyCollection使用的垃圾回收器可用內存比MyCollection更快?

編輯 :到目前為止,人們已經發布了一些使用IDisposable清理非託管資源(如數據庫連接和位​​圖)的好例子。 但是,假設上述代碼中的_theList包含一百萬個字符串,並且您現在想要釋放該內存,而不是等待垃圾收集器。 上面的代碼會完成那個嗎?


Dispose模式的目的是提供一種清理託管資源和非託管資源的機制,以及何時發生取決於如何調用Dispose方法。 在你的例子中,Dispose的使用實際上並沒有做與處理有關的任何事情,因為清除列表對處理該集合沒有影響。 同樣,將變量設置為null的調用也不會影響GC。

你可以看看這篇article ,了解如何實現Dispose模式的更多細節,但它基本如下所示:

public class SimpleCleanup : IDisposable
{
    // some fields that require cleanup
    private SafeHandle handle;
    private bool disposed = false; // to detect redundant calls

    public SimpleCleanup()
    {
        this.handle = /*...*/;
    }

    protected virtual void Dispose(bool disposing)
    {
        if (!disposed)
        {
            if (disposing)
            {
                // Dispose managed resources.
                if (handle != null)
                {
                    handle.Dispose();
                }
            }

            // Dispose unmanaged managed resources.

            disposed = true;
        }
    }

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

這裡最重要的方法是Dispose(bool),它實際上在兩種不同的情況下運行:

  • 處置== true:該方法直接或間接由用戶代碼調用。 可以處置託管和非託管資源。
  • 處置== false:該方法已由運行時從終結器中調用,並且不應引用其他對象。 只能處理非託管資源。

簡單地讓GC負責清理的問題是,您無法真正控制GC何時運行一個收集週期(您可以調用GC.Collect(),但實際上不應該這樣做),因此資源可能會停留比需要的時間長。 請記住,調用Dispose()實際上並不會導致收集週期或以任何方式導致GC收集/釋放對象; 它只是提供了更確定地清理所使用的資源的方法,並告訴GC該清理已經執行。

IDisposable的全部和配置模式並不是立即釋放內存。 唯一一次對Dispose的調用實際上甚至有可能立即釋放內存的時間是處理處置== false場景和處理非託管資源的時間。 對於託管代碼,直到GC運行一個你真正無法控制的採集週期(除了調用GC.Collect(),我已經提到這不是一個好主意)之前,內存實際上不會被回收。

你的場景並不真正有效,因為.NET中的字符串沒有使用任何未經處理的資源,也沒有實現IDisposable,所以沒有辦法強制它們被“清理”。


Dispose的要點釋放非託管資源。 它需要在某個時候完成,否則它們將永遠不會被清理。 垃圾收集器不知道如何IntPtr類型的變量上調用DeleteHandle() ,它不知道是否需要調用DeleteHandle()

注意 :什麼是非託管資源 ? 如果您在Microsoft .NET Framework中找到它:它是託管的。 如果你自己去了解MSDN,那麼它是非託管的。 任何你使用過P / Invoke調用的東西都不在.NET框架中可用的所有可用的舒適的世界之外,而且你現在負責清理它。

你創建的對象需要公開一些方法,外部世界可以調用,以清理非託管資源。 該方法可以任意命名:

public void Cleanup()

public void Shutdown()

相反,這種方法有一個標準化的名稱:

public void Dispose()

甚至有一個創建的接口, IDisposable ,只有一個方法:

public interface IDisposable
{
   void Dispose()
}

所以你讓你的對象公開了IDisposable接口,這樣你就保證你已經編寫了這個單一的方法來清理你的非託管資源:

public void Dispose()
{
   Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle);
}

你完成了。 除非你能做得更好。

如果你的對像已經分配了一個250MB的System.Drawing.Bitmap (即.NET管理的Bitmap類)作為某種幀緩衝區? 當然,這是一個託管的.NET對象,垃圾收集器將釋放它。 但是,你真的想離開250MB的內存嗎?等待垃圾收集器最終來到並釋放它? 如果有一個開放的數據庫連接會怎麼樣? 當然,我們不希望這個連接處於打開狀態,等待GC完成對象。

如果用戶調用Dispose() (意味著他們不再計劃使用該對象),為什麼不去掉這些浪費的位圖和數據庫連接呢?

所以現在我們會:

  • 擺脫非託管資源(因為我們必須)和
  • 擺脫管理資源(因為我們希望有所幫助)

所以讓我們更新我們的Dispose()方法來擺脫這些管理對象:

public void Dispose()
{
   //Free unmanaged resources
   Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle);

   //Free managed resources too
   if (this.databaseConnection != null)
   {
      this.databaseConnection.Dispose();
      this.databaseConnection = null;
   }
   if (this.frameBufferImage != null)
   {
      this.frameBufferImage.Dispose();
      this.frameBufferImage = null;
   }
}

一切都很好, 除非你能做得更好

如果該人員忘記在您的對像上調用Dispose()會怎麼樣? 然後他們會洩露一些非託管資源!

注意:它們不會洩漏託管資源,因為垃圾收集器最終將在後台線程上運行,並釋放與任何未使用的對象關聯的內存。 這將包括您的對象和您使用的任何管理對象(例如BitmapDbConnection )。

如果這個人忘了叫Dispose() ,我們仍然可以保存他們的培根! 我們仍然有一種方法可以他們調用它:當垃圾回收器最終獲得釋放(即最終化)我們的對象時。

注意:垃圾收集器最終將釋放所有管理對象。 當它發生時,它會調用對像上的Finalize方法。 GC不知道或關心Dispose方法。 這只是我們選擇的一個名稱,當我們想要擺脫不受管理的東西時,我們稱之為方法。

垃圾收集器破壞我們的對像是釋放這些煩人的非託管資源的最佳時機。 我們通過重寫Finalize()方法來做到這一點。

注意:在C#中,您沒有顯式重寫Finalize()方法。 您編寫一個看起來像 C ++析構函數的方法,編譯器將其作為Finalize()方法的實現:

~MyObject()
{
    //we're being finalized (i.e. destroyed), call Dispose in case the user forgot to
    Dispose(); //<--Warning: subtle bug! Keep reading!
}

但是該代碼中存在一個錯誤。 你看,垃圾收集器運行在後台線程上 ; 你不知道兩個對像被銷毀的順序。 在您的Dispose()代碼中,完全可能的是,您試圖擺脫的託管對象(因為您希望有所幫助)不再存在:

public void Dispose()
{
   //Free unmanaged resources
   Win32.DestroyHandle(this.gdiCursorBitmapStreamFileHandle);

   //Free managed resources too
   if (this.databaseConnection != null)
   {
      this.databaseConnection.Dispose(); //<-- crash, GC already destroyed it
      this.databaseConnection = null;
   }
   if (this.frameBufferImage != null)
   {
      this.frameBufferImage.Dispose(); //<-- crash, GC already destroyed it
      this.frameBufferImage = null;
   }
}

因此,您需要的是Finalize()告訴Dispose()它不應該觸及任何託管資源(因為它們可能不在那裡 ),同時仍釋放非託管資源。

這樣做的標準模式是讓Finalize()Dispose()都調用第三個 (!)方法; 如果您從Dispose() (而不是Finalize() )調用它,則傳遞布爾值,這意味著釋放託管資源是安全的。

這個內部方法可以被賦予一些任意的名字,比如“CoreDispose”或者“MyInternalDispose”,但是傳統上稱之為Dispose(Boolean)

protected void Dispose(Boolean disposing)

但更有用的參數名稱可能是:

protected void Dispose(Boolean itIsSafeToAlsoFreeManagedObjects)
{
   //Free unmanaged resources
   Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle);

   //Free managed resources too, but only if I'm being called from Dispose
   //(If I'm being called from Finalize then the objects might not exist
   //anymore
   if (itIsSafeToAlsoFreeManagedObjects)  
   {    
      if (this.databaseConnection != null)
      {
         this.databaseConnection.Dispose();
         this.databaseConnection = null;
      }
      if (this.frameBufferImage != null)
      {
         this.frameBufferImage.Dispose();
         this.frameBufferImage = null;
      }
   }
}

並且您將IDisposable.Dispose()方法的實現更改為:

public void Dispose()
{
   Dispose(true); //I am calling you from Dispose, it's safe
}

和你的終結者:

~MyObject()
{
   Dispose(false); //I am *not* calling you from Dispose, it's *not* safe
}

注意 :如果您的對像從實現Dispose對像下降,那麼當您重寫Dispose時,請不要忘記調用它們的基本 Dispose方法:

public Dispose()
{
    try
    {
        Dispose(true); //true: safe to free managed resources
    }
    finally
    {
        base.Dispose();
    }
}

一切都很好, 除非你能做得更好

如果用戶在對像上調用Dispose() ,則所有內容都已清理完畢。 稍後,當垃圾收集器出現並調用Finalize時,它將再次調用Dispose

這不僅浪費,而且如果你的對像有垃圾引用,這些對像是你從上次調用Dispose()已經處理過的對象,那麼你將嘗試再次處理它們!

你會注意到在我的代碼中我很小心地移除了我已經Dispose的對象的引用,所以我不試圖在垃圾對象引用上調用Dispose 。 但是這並沒有阻止一個微妙的錯誤蔓延。

當用戶調用Dispose() ,句柄CursorFileBitmapIconServiceHandle被銷毀。 稍後當垃圾收集器運行時,它將嘗試再次銷毀相同的句柄。

protected void Dispose(Boolean iAmBeingCalledFromDisposeAndNotFinalize)
{
   //Free unmanaged resources
   Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle); //<--double destroy 
   ...
}

你解決這個問題的方式是告訴垃圾收集器它不需要費心去完成對象 - 它的資源已經被清理了,不需要更多的工作。 您可以通過在Dispose()方法中調用GC.SuppressFinalize()來完成此操作:

public void Dispose()
{
   Dispose(true); //I am calling you from Dispose, it's safe
   GC.SuppressFinalize(this); //Hey, GC: don't bother calling finalize later
}

現在用戶調用了Dispose() ,我們有:

  • 釋放非託管資源
  • 釋放管理資源

GC運行終結器沒有意義 - 一切都照顧好了。

我無法使用Finalize來清理非託管資源嗎?

Object.Finalize的文檔說:

Finalize方法用於在對象銷毀之前對當前對象持有的非託管資源執行清理操作。

但MSDN文檔也說,對於IDisposable.Dispose

執行與釋放,釋放或重置非託管資源相關的應用程序定義的任務。

那它是哪一個? 哪一個是我清理非託管資源的地方? 答案是:

這是你的選擇! 但選擇Dispose

您當然可以將您的非託管清理放入終結器中:

~MyObject()
{
   //Free unmanaged resources
   Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle);

   //A C# destructor automatically calls the destructor of its base class.
}

這個問題是你不知道什麼時候垃圾收集器會繞過來完成你的對象。 您的未管理的,不需要的,未使用的本地資源將一直存在,直到垃圾收集器最終運行。 然後它會調用你的終結器方法; 清理非託管資源。 Object.Finalize的文檔指出:

終結器執行的確切時間是未定義的。 為確保為您的類的實例確定性地釋放資源,請實現一個Close方法或提供一個IDisposable.Dispose實現。

這是使用Dispose清理非託管資源的優點; 你會知道,並控制,當非託管資源被清理。 他們的破壞是“確定性的”

回答你原來的問題:為什麼不現在釋放記憶,而不是當GC決定這麼做? 我有一個面部識別軟件,現在需要擺脫530 MB的內部圖像,因為它們不再需要。 當我們不這樣做時:機器會停下來交換。

獎金閱讀

對於喜歡這個答案的風格的人(解釋為什麼 ,所以如何變得明顯),我建議你閱讀Don Box的基本COM的第一章:

在35頁中,他解釋了使用二進制對象的問題,並在您的眼前發明COM。 一旦你意識到COM的原因 ,其餘的300頁是顯而易見的,只是詳細描述了微軟的實施。

我認為每個曾經處理過對像或COM的程序員都應該至少閱讀第一章。 這是有史以​​來最好的解釋。

額外的獎金閱讀

當你知道的所有事情都是由Eric Lippert 錯誤

因此,編寫一份正確的終結器確實非常困難, 我可以給你的最好建議是不要嘗試


Apart from its primary use as a way to control the lifetime of system resources (completely covered by the awesome answer of Ian , kudos!), the IDisposable/using combo can also be used to scope the state change of (critical) global resources : the console , the threads , the process , any global object like an application instance .

I've written an article about this pattern: http://pragmateek.com/c-scope-your-global-state-changes-with-idisposable-and-the-using-statement/

It illustrates how you can protect some often used global state in a reusable and readable manner: console colors , current thread culture , Excel application object properties ...


First of definition. For me unmanaged resource means some class, which implements IDisposable interface or something created with usage of calls to dll. GC doesn't know how to deal with such objects. If class has for example only value types, then I don't consider this class as class with unmanaged resources. For my code I follow next practices:

  1. If created by me class uses some unmanaged resources then it means that I should also implement IDisposable interface in order to clean memory.
  2. Clean objects as soon as I finished usage of it.
  3. In my dispose method I iterate over all IDisposable members of class and call Dispose.
  4. In my Dispose method call GC.SuppressFinalize(this) in order to notify garbage collector that my object was already cleaned up. I do it because calling of GC is expensive operation.
  5. As additional precaution I try to make possible calling of Dispose() multiple times.
  6. Sometime I add private member _disposed and check in method calls did object was cleaned up. And if it was cleaned up then generate ObjectDisposedException
    Following template demonstrates what I described in words as sample of code:

public class SomeClass : IDisposable
    {
        /// <summary>
        /// As usually I don't care was object disposed or not
        /// </summary>
        public void SomeMethod()
        {
            if (_disposed)
                throw new ObjectDisposedException("SomeClass instance been disposed");
        }

        public void Dispose()
        {
            Dispose(true);
        }

        private bool _disposed;

        protected virtual void Dispose(bool disposing)
        {
            if (_disposed)
                return;
            if (disposing)//we are in the first call
            {
            }
            _disposed = true;
        }
    }

If anything, I'd expect the code to be less efficient than when leaving it out.

Calling the Clear() methods are unnecessary, and the GC probably wouldn't do that if the Dispose didn't do it...



One problem with most discussions of "unmanaged resources" is that they don't really define the term, but seem to imply that it has something to do with unmanaged code. While it is true that many types of unmanaged resources do interface with unmanaged code, thinking of unmanaged resources in such terms isn't helpful.

Instead, one should recognize what all managed resources have in common: they all entail an object asking some outside 'thing' to do something on its behalf, to the detriment of some other 'things', and the other entity agreeing to do so until further notice. If the object were to be abandoned and vanish without a trace, nothing would ever tell that outside 'thing' that it no longer needed to alter its behavior on behalf of the object that no longer existed; consequently, the 'thing's usefulness would be permanently diminished.

An unmanaged resource, then, represents an agreement by some outside 'thing' to alter its behavior on behalf of an object, which would useless impair the usefulness of that outside 'thing' if the object were abandoned and ceased to exist. A managed resource is an object which is the beneficiary of such an agreement, but which has signed up to receive notification if it is abandoned, and which will use such notification to put its affairs in order before it is destroyed.


The most justifiable use case for disposal of managed resources, is preparation for the GC to reclaim resources that would otherwise never be collected.

A prime example is circular references.

Whilst it's best practice to use patterns that avoid circular references, if you do end up with (for example) a 'child' object that has a reference back to its 'parent', this can stop GC collection of the parent if you just abandon the reference and rely on GC - plus if you have implemented a finalizer, it'll never be called.

The only way round this is to manually break the circular references by setting the Parent references to null on the children.

Implementing IDisposable on parent and children is the best way to do this. When Dispose is called on the Parent, call Dispose on all Children, and in the child Dispose method, set the Parent references to null.


Yep, that code is completely redundant and unnecessary and it doesn't make the garbage collector do anything it wouldn't otherwise do (once an instance of MyCollection goes out of scope, that is.) Especially the .Clear() calls.

Answer to your edit: Sort of. If I do this:

public void WasteMemory()
{
    var instance = new MyCollection(); // this one has no Dispose() method
    instance.FillItWithAMillionStrings();
}

// 1 million strings are in memory, but marked for reclamation by the GC

It's functionally identical to this for purposes of memory management:

public void WasteMemory()
{
    var instance = new MyCollection(); // this one has your Dispose()
    instance.FillItWithAMillionStrings();
    instance.Dispose();
}

// 1 million strings are in memory, but marked for reclamation by the GC

If you really really really need to free the memory this very instant, call GC.Collect() . There's no reason to do this here, though. The memory will be freed when it's needed.


在調用Dispose之後,不應再有對對象的方法的調用(儘管對象應該允許對Dispose的進一步調用)。 因此這個問題中的例子很愚蠢。 如果調用Dispose,那麼該對象本身可以被丟棄。 所以用戶應該放棄對整個對象的所有引用(將它們設置為空),並且內部的所有相關對像都會自動清理。

至於關於託管/非託管以及其他答案中的討論的一般性問題,我認為對這個問題的任何回答都必須從非託管資源的定義開始。

歸結起來,有一個函數可以調用來使系統進入狀態,還有另一個函數可以調用以使其退出該狀態。 現在,在典型的例子中,第一個可能是一個返回文件句柄的函數,第二個可能是對CloseHandle的調用。

但是 - 這是關鍵 - 它們可以是任何匹配的功能對。 一個建立一個國家,另一個將其撕下。 如果該州已建成但尚未拆除,則存在該資源的實例。 您必須安排在合適的時間進行拆卸 - 資源不由CLR管理。 唯一自動管理的資源類型是內存。 有兩種:GC和堆棧。 值類型由堆棧管理(或通過在引用類型內部搭建),引用類型由GC管理。

這些函數可能會導致可以自由交錯的狀態更改,或者可能需要完全嵌套。 狀態更改可能是線程安全的,也可能不是。

看看正義問題中的例子。 對日誌文件的縮進進行的更改必須完美嵌套,否則一切都會出錯。 他們也不太可能是線程安全的。

有可能搭上垃圾收集器來清理你的非託管資源。 但是,只有當狀態改變函數是線程安全的並且兩個狀態可以有任何重疊的生命週期時。 所以正義的資源的例子不能有一個終結者! 它不會幫助任何人。

對於這些資源,您可以實現IDisposable ,無需終結器。 終結者是絕對可選的 - 它必須是。 這在許多書中都被掩蓋了,甚至沒有提及。

然後您必須使用using語句來確保調用Dispose 。 這基本上就像搭上堆棧一樣(因為終結器是GC, using是堆棧)。

缺少的部分是您必須手動編寫Dispose並將其調用到您的字段和基類中。 C ++ / CLI程序員不必這樣做。 編譯器在大多數情況下為它們編寫它。

還有一個替代方案,我更喜歡那些嵌套完美且不是線程安全的狀態(除了別的之外,避免使用IDisposable來替代無法為每個實現IDisposable的類添加終結器的人發生爭論的問題) 。

寫一個函數,而不是寫一個類。 該函數接受委託來回叫:

public static void Indented(this Log log, Action action)
{
    log.Indent();
    try
    {
        action();
    }
    finally
    {
        log.Outdent();
    }
}

然後一個簡單的例子是:

Log.Write("Message at the top");
Log.Indented(() =>
{
    Log.Write("And this is indented");

    Log.Indented(() =>
    {
        Log.Write("This is even more indented");
    });
});
Log.Write("Back at the outermost level again");

被傳入的lambda用作代碼塊,因此它就像您自己的控制結構與using相同的目的一樣,除了您不再有主叫方濫用它的危險之外。 他們無法清理資源。

如果資源是可能具有重疊生命期的那種,那麼這種技術就不那麼有用了,因為那時你想要能夠建立資源A,然後資源B,然後殺死資源A,然後殺死資源B.你不能那樣做如果你迫使用戶完全像這樣嵌套。 但是那麼你需要使用IDisposable (但是仍然沒有終結器,除非你已經實現了threadsafety,它不是免費的)。


我使用IDisposable方案:清理非託管資源,取消訂閱事件,關閉連接

我用於實現IDisposable( 不是線程安全 )的習慣用法:

class MyClass : IDisposable {
    // ...

    #region IDisposable Members and Helpers
    private bool disposed = false;

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

    private void Dispose(bool disposing) {
        if (!this.disposed) {
            if (disposing) {
                // cleanup code goes here
            }
            disposed = true;
        }
    }

    ~MyClass() {
        Dispose(false);
    }
    #endregion
}

通常使用IDisposable來利用using語句,並利用一種簡單的方法對託管對象進行確定性清理。

public class LoggingContext : IDisposable {
    public Finicky(string name) {
        Log.Write("Entering Log Context {0}", name);
        Log.Indent();
    }
    public void Dispose() {
        Log.Outdent();
    }

    public static void Main() {
        Log.Write("Some initial stuff.");
        try {
            using(new LoggingContext()) {
                Log.Write("Some stuff inside the context.");
                throw new Exception();
            }
        } catch {
            Log.Write("Man, that was a heavy exception caught from inside a child logging context!");
        } finally {
            Log.Write("Some final stuff.");
        }
    }
}




idisposable