必须在C#中手动清理的资源?

时间:2020-03-06 14:33:40  来源:igfitidea点击:

必须在Cand中手动清理哪些资源,否则将导致什么后果?

例如,说我有以下代码:

myBrush = new System.Drawing.SolidBrush(System.Drawing.Color.Black);
// Use Brush

如果我不使用dispose方法清理画笔,那我假设垃圾收集器释放了程序终止时使用的内存吗?这样对吗?

我还需要手动清理哪些其他资源?

解决方案

  • 处理内部Windows数据结构。
  • 数据库连接。
  • 文件句柄。
  • 网络连接。
  • COM / OLE引用。

清单继续。

重要的是调用Dispose甚至更好,使用using模式。

using (SolidBrush myBrush = new System.Drawing.SolidBrush(System.Drawing.Color.Black))
{
    // use myBrush
}

如果我们不处理任何东西,则当垃圾收集器发现不再有对其的引用时(可能会在一段时间后),将对其进行清理。

对于" System.Drawing.Brush",Windows将在内存中保留画笔的内部Windows结构,直到所有程序释放其句柄为止。

通常,任何实现IDisposable的东西都应导致我们暂停并研究正在使用的资源。

GC仅在存在内存压力时发生,因此我们无法预测何时。尽管卸载AppDomain肯定会触发它。

垃圾收集器将处理所有托管资源。在示例中,当垃圾收集器决定清理画笔时,将在对画笔的最后一次引用不再有效之后的一段时间内进行清理。

有些事情需要手动清理,但是这些都是从非托管源中检索的指针,例如DLL调用,但是.NET Framework中没有任何东西需要这种处理。

好吧,只要我们使用资源的托管版本并且不自己调用Windows API,就可以了。当我们得到的是IntPtr时,只需要担心必须删除/销毁资源,因为在.NET中" Windows句柄"(以及很多其他东西)是已知的,而不是对象。

顺便说一句,资源(与任何其他.NET对象一样)将在离开当前上下文后立即标记为收集,因此,如果在方法内部创建Brush,则在退出该方法时将标记该资源。

如果我们不处理任何东西,则当垃圾收集器发现代码中不再有对其的引用时(可能会在一段时间后),将对其进行清理。对于这样的事情,这并不重要,但是对于一个打开的文件却可能如此。

通常,如果某事物具有Dispose方法,则应在完成后调用它,或者,如果可以的话,将其包装在using语句中:

using (SolidBrush myBrush = new System.Drawing.SolidBrush(System.Drawing.Color.Black))
{
    // use myBrush
}

如果它是托管的(即框架的一部分),则无需担心。如果它实现IDisposable,则将其包装在一个using块中。

如果要使用非托管资源,则需要阅读终结器并自己实现IDisposable。

这个问题下还有很多细节

从技术上讲,从IDisposable继承的任何内容都应积极处置。我们可以使用" using"语句使事情变得更容易。

http://msdn.microsoft.com/zh-CN/library/yh598w02.aspx

有时我们会在文档示例代码以及工具(即Visual Studio)生成的代码中看到IDisposable派生对象的使用不一致。

IDisposable的优点是,它使我们能够主动释放基础非托管资源。有时我们确实想这样做,例如考虑网络连接和文件资源。

首先,在程序终止时,我们可以假定该进程使用的内存将与该进程本身一起消除。

在.net中使用Dispose或者Destructor时,必须了解GC调用Dispose函数的时间是不确定的。那就是为什么建议显式使用using或者调用Dispose的原因。

当使用文件之类的资源时,必须释放内存对象(例如,信号量和.net托管范围之外的资源)。

以SolidBrush为例,我们需要进行处理,因为它是GDI对象,并且位于.net世界之外。

垃圾收集器不仅会在程序终止时释放空间,否则它就不会真正有用(在任何体面/最新的OS上,当进程退出时,无论如何操作系统都会自动清除其所有内存)。

与C / C ++相比,C的最大优点之一是我们不必担心释放分配的对象(至少在大多数情况下); gc在运行时决定时执行此操作(各种策略何时/如何执行)。

gc无法处理许多资源:文件,与线程相关的资源(锁),网络连接等...

不处理IDisposable的后果可能会导致可忽略的性能下降甚至导致应用崩溃。

示例中的Brush对象将在感觉良好时由GC清理。但是,程序将无法享受之前清理该程序所获得的额外内存。如果我们使用大量的Brush对象,这可能会变得很重要。如果对象的存在时间不长,GC清理对象的效率也会更高,因为它是世代垃圾收集器。

另一方面,不处理数据库连接对象的后果可能意味着我们很快耗尽了池化数据库连接,并导致应用程序崩溃。

无论使用

using (new DisposableThing...
{
    ...
}

或者,如果我们需要在对象的生存期内保留对IDisposable的引用,请在对象上实现IDisposable并调用IDisposable的Dispose方法。

class MyClass : IDisposable
{
    private IDisposable disposableThing;

    public void DoStuffThatRequiresHavingAReferenceToDisposableThing() { ... }

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

    protected virtual void Dispose(bool disposing)
    //etc... (see IDisposable on msdn)

}

要注意的一个地方是对象看起来对GC来说很小,但不是。...例如,在SharePoint API中,就GC而言,SPWeb对象的占地面积很小,因此收集的优先级较低,但是确实抓住了GC不知道的一堆内存(我相信是在堆中)。例如,如果要讲很多这些,就会遇到一些有趣的内存问题,请务必记住要使用use或者dispose!

正如其他人所说,使用是朋友。
我写了这个博客文章,内容是关于如何以一种非常直接的方式实现IDisposable的方式,该方式通过剔除最重要的部分而减少了出错的可能性。

当我不记得给定对象是否为可处理资源时,我使用的一个技巧是在声明后键入" .Dispose"(最多!),以使Intellisense为我检查:

MemoryStream ms = new MemoryStream().Dispose

然后删除.Dispose并使用using()指令:

using(MemoryStream ms = new MemoryStream())
{
  ...
}

与其将一个对象视为需要释放的"持有"资源,不如将一个对象视为已经改变了某些属性(可能在计算机外部!),而这种改变会使该对象失效,而不是认为这是有害的。撤消或者"清理",但只有对象可以清理。尽管此更改通常采用标记为"忙碌"的池中某些具体对象的形式,但其精确形式并不重要。重要的是,需要撤消更改,并且对象拥有执行此操作所需的信息。