通过阅读Microsoft文档,我知道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使用的垃圾收集器释放内存比正常情况更快?


编辑:到目前为止,人们已经发布了一些使用IDisposable清理非托管资源(如数据库连接和位图)的好例子。但假设上述代码中的_theList包含一百万个字符串,并且您希望现在释放内存,而不是等待垃圾收集器。上面的代码能做到这一点吗?


当前回答

Dispose模式的目的是提供一种清理托管和非托管资源的机制,何时发生取决于Dispose方法的调用方式。在您的示例中,Dispose的使用实际上并没有执行任何与Dispose相关的操作,因为清除列表对正在处理的集合没有影响。同样,将变量设置为null的调用对GC也没有影响。

关于如何实现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),它实际上在两种不同的情况下运行:

dispositing==true:用户代码直接或间接调用了该方法。可以释放托管和非托管资源。dispositing==false:该方法已由运行时从终结器内部调用,不应引用其他对象。只能释放非托管资源。

简单地让GC负责清理的问题是,你无法真正控制GC何时运行一个收集周期(你可以调用GC.Collect(),但你真的不应该这样做),所以资源可能会比需要的时间更长。记住,调用Dispose()实际上不会导致收集循环,也不会以任何方式导致GC收集/释放对象;它只是提供了更明确地清理所用资源的方法,并告诉GC该清理已经执行。

IDisposable和dispose模式的重点不是立即释放内存。对Dispose的调用实际上只有在处理dispositing==false场景和处理非托管资源时才有机会立即释放内存。对于托管代码,在GC运行一个收集循环之前,内存实际上不会被回收,这是您无法控制的(除了调用GC.Collect()之外,我已经提到过这不是一个好主意)。

由于.NET中的字符串不使用任何未更改的资源,也不实现IDisposable,因此您的方案并不真正有效,因此无法强制“清理”它们

其他回答

IDisposable/using组合除了主要用作控制系统资源生命周期的方法(完全被Ian的精彩回答所涵盖)外,还可以用于确定(关键)全局资源的状态变化范围:控制台、线程、进程、任何全局对象(如应用程序实例)。

我写了一篇关于这种模式的文章:http://pragmateek.com/c-scope-your-global-state-changes-with-idisposable-and-the-using-statement/

它说明了如何以可重用和可读的方式保护一些常用的全局状态:控制台颜色、当前线程区域性、Excel应用程序对象财产。。。

在您发布的示例中,它仍然没有“立即释放内存”。所有内存都是垃圾收集的,但它可能允许在早期版本中收集内存。你必须运行一些测试才能确定。


框架设计指南是指南,而不是规则。它们告诉你界面主要用于什么,何时使用,如何使用,以及何时不使用。

我曾经读过一段代码,它是一个利用IDisposable失败时的简单RollBack()。下面的MiniTx类将检查Dispose()上的一个标志,如果Commit调用从未发生,它将调用回滚。它添加了一层间接层,使调用代码更易于理解和维护。结果如下:

using( MiniTx tx = new MiniTx() )
{
    // code that might not work.

    tx.Commit();
} 

我也看到计时/日志代码做了同样的事情。在这种情况下,Dispose()方法停止计时器并记录块已退出。

using( LogTimer log = new LogTimer("MyCategory", "Some message") )
{
    // code to time...
}

因此,这里有几个具体的示例,它们不执行任何非托管资源清理,但成功地使用IDisposable创建了更干净的代码。

我使用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用法的好示例。字典清除通常不应转到Dispose方法。字典项在超出范围时将被清除和处理。需要IDisposable实现来释放一些内存/处理程序,即使它们超出范围也不会释放/释放。

下面的示例显示了IDisposable模式的一个很好的示例,其中包含一些代码和注释。

public class DisposeExample
{
    // A base class that implements IDisposable. 
    // By implementing IDisposable, you are announcing that 
    // instances of this type allocate scarce resources. 
    public class MyResource: IDisposable
    {
        // Pointer to an external unmanaged resource. 
        private IntPtr handle;
        // Other managed resource this class uses. 
        private Component component = new Component();
        // Track whether Dispose has been called. 
        private bool disposed = false;

        // The class constructor. 
        public MyResource(IntPtr handle)
        {
            this.handle = handle;
        }

        // Implement IDisposable. 
        // Do not make this method virtual. 
        // A derived class should not be able to override this method. 
        public void Dispose()
        {
            Dispose(true);
            // This object will be cleaned up by the Dispose method. 
            // Therefore, you should call GC.SupressFinalize to 
            // take this object off the finalization queue 
            // and prevent finalization code for this object 
            // from executing a second time.
            GC.SuppressFinalize(this);
        }

        // Dispose(bool disposing) executes in two distinct scenarios. 
        // If disposing equals true, the method has been called directly 
        // or indirectly by a user's code. Managed and unmanaged resources 
        // can be disposed. 
        // If disposing equals false, the method has been called by the 
        // runtime from inside the finalizer and you should not reference 
        // other objects. Only unmanaged resources can be disposed. 
        protected virtual void Dispose(bool disposing)
        {
            // Check to see if Dispose has already been called. 
            if(!this.disposed)
            {
                // If disposing equals true, dispose all managed 
                // and unmanaged resources. 
                if(disposing)
                {
                    // Dispose managed resources.
                    component.Dispose();
                }

                // Call the appropriate methods to clean up 
                // unmanaged resources here. 
                // If disposing is false, 
                // only the following code is executed.
                CloseHandle(handle);
                handle = IntPtr.Zero;

                // Note disposing has been done.
                disposed = true;

            }
        }

        // Use interop to call the method necessary 
        // to clean up the unmanaged resource.
        [System.Runtime.InteropServices.DllImport("Kernel32")]
        private extern static Boolean CloseHandle(IntPtr handle);

        // Use C# destructor syntax for finalization code. 
        // This destructor will run only if the Dispose method 
        // does not get called. 
        // It gives your base class the opportunity to finalize. 
        // Do not provide destructors in types derived from this class.
        ~MyResource()
        {
            // Do not re-create Dispose clean-up code here. 
            // Calling Dispose(false) is optimal in terms of 
            // readability and maintainability.
            Dispose(false);
        }
    }
    public static void Main()
    {
        // Insert code here to create 
        // and use the MyResource object.
    }
}