通过阅读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包含一百万个字符串,并且您希望现在释放内存,而不是等待垃圾收集器。上面的代码能做到这一点吗?


当前回答

我使用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。我认为这篇文章是我找到的关于如何实际使用IDisposable的最佳解释之一。

https://www.codeproject.com/Articles/29534/IDisposable-What-Your-Mother-Never-Told-You-About

对于实际问题;如果您使用IDisposable清理占用大量内存的托管对象,简短的答案是否定的。原因是,一旦持有内存的对象超出范围,它就可以进行收集了。此时,任何引用的子对象也超出范围,将被收集。

唯一真正的例外是,如果托管对象中占用了大量内存,并且您已经阻止了该线程等待某个操作完成。如果在调用完成后不需要这些对象,那么将这些引用设置为null可能会让垃圾收集器更快地收集它们。但那个场景将代表需要重构的坏代码——而不是IDisposable的用例。

给定的代码示例不是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.
    }
}

首先是定义。对我来说,非托管资源意味着某种类,它实现了IDisposable接口或使用对dll的调用创建的东西。GC不知道如何处理此类对象。例如,如果类只有值类型,那么我不认为这个类是具有非托管资源的类。对于我的代码,我遵循以下实践:

如果由我创建的类使用一些非托管资源,那么这意味着我还应该实现IDisposable接口以清理内存。使用完后立即清理对象。在dispose方法中,我遍历类的所有IDisposable成员并调用dispose。在Dispose方法中,调用GC.SuppressFinalize(this),以通知垃圾收集器我的对象已被清理。我这样做是因为调用GC是一项昂贵的操作。作为额外的预防措施,我尝试多次调用Dispose()。有时我添加私有成员_disposed并签入方法调用,但对象被清理了。如果已清理,则生成ObjectDisposedException以下模板演示了我用文字描述的代码示例:

公共类SomeClass:IDisposable{///<summary>///像往常一样,我不在乎对象是否被处理///</summary>public void SomeMethod(){如果(_disposed)引发新的ObjectDisposedException(“已释放SomeClass实例”);}public void Dispose(){Dispose(真);}私有bool _disposed;受保护的虚拟void Dispose(bool dispositing){如果(_disposed)回来如果(处置)//我们在第一次通话中{}_已处理=真;}}

如果MyCollection无论如何都将被垃圾收集,那么您不需要对其进行处理。这样做只会过度消耗CPU,甚至可能使垃圾收集器已经执行的一些预先计算的分析无效。

我使用IDisposable来执行诸如确保线程以及非托管资源被正确处理之类的操作。

编辑回应斯科特的评论:

GC性能指标唯一受到影响的时间是调用〔sic〕GC.Collect()时“

从概念上讲,GC维护对象引用图的视图,以及线程堆栈帧中对它的所有引用。这个堆可能非常大,并跨越许多页面的内存。作为优化,GC缓存对不太可能经常更改的页面的分析,以避免不必要地重新扫描页面。当页面中的数据发生更改时,GC从内核接收通知,因此它知道页面已脏,需要重新扫描。如果集合在Gen0中,那么页面中的其他内容也可能发生变化,但在Gen1和Gen2中这种情况发生的可能性较小。有趣的是,对于将GC移植到Mac以使Silverlight插件在该平台上工作的团队来说,这些钩子在Mac OS X中不可用。

反对不必要的资源处置的另一点是:想象一个进程正在卸载的情况。想象一下,这个过程已经运行了一段时间。很可能该进程的许多内存页已被交换到磁盘。至少它们不再位于L1或L2缓存中。在这种情况下,正在卸载的应用程序没有必要将所有数据和代码页交换回内存,以“释放”进程终止时操作系统无论如何都会释放的资源。这适用于托管资源,甚至某些非托管资源。只有使非后台线程保持活动状态的资源才能被释放,否则进程将保持活动状态。

现在,在正常执行期间,必须正确清理临时资源(正如@fezmonkey指出的数据库连接、套接字和窗口句柄),以避免非托管内存泄漏。这些都是必须处理的事情。如果您创建了一个拥有线程的类(我的意思是它创建了线程,因此负责确保它停止,至少按照我的编码风格),那么该类很可能必须实现IDisposable,并在Dispose期间拆除线程。

.NET框架使用IDisposable接口作为开发人员必须释放此类的信号,甚至是警告。我想不出框架中实现IDisposable的任何类型(不包括显式接口实现),其中处置是可选的。