C#内存和dispose相关问题
Posted
技术标签:
【中文标题】C#内存和dispose相关问题【英文标题】:C# memory and dispose related questions 【发布时间】:2010-12-29 15:34:57 【问题描述】:我有以下代码,只是想检查谁会调用 dispose?是不是自动调用的。
ToolTip toolTip = new ToolTip();
toolTip.SetToolTip(button, toolTipText);
还假设我创建了一个 Timer 局部变量,谁将调用 dispose,内存泄漏怎么办,因为如果我立即调用 dispose,则不会触发计时器事件。
我是否需要确保在计时器事件处理程序中调用 dispose,即使我没有对计时器变量的任何引用。我还需要注销该计时器的事件处理程序吗?
编辑:
但是如何在 ToolTip 上调用 dispose,如果我调用它不会显示。 如果我没有任何参考资料,还有为什么我需要处置计时器。 我还需要注销计时器事件处理程序吗? 保留引用也会增加类内存占用。【问题讨论】:
【参考方案1】:ToolTip 在显示它的控件上注册事件处理程序。当表单的 Deactivate 事件触发时,它的 Hide() 方法会被自动调用。当表单关闭时会发生这种情况。这反过来又确保了它的 Windows 句柄被销毁并且事件处理程序被取消注册。之后就没有一次性物品了。
您可以使用 Reflector 或 Reference Source 自己验证这一点。相关方法依次为 BaseFormDeactivate、HideAllToolTips、Hide、ClearTopLevelControlEvents。
你不必调用 Dispose(),你没有泄漏。
【讨论】:
这里完全糊涂了,你说不需要,其他帖子里的人说需要。另外如果我没有对计时器类的任何引用,它会在事件触发后自动GC吗? 我认为他们没有查看代码。如果没有对它的引用,则每个非静态对象都会被 GC 处理。【参考方案2】:Dispose 是提供的一种方法,可让您立即释放对象。例如独占文件流。
Finalize 是一种方法,一旦您的程序不再使用该对象正在使用的任何资源,它就会自动释放它。
您只需要担心对象使用独占或有限资源时的处置。示例是文件一或数据库连接。 (注意,在这两种情况下,Close 等价于 disposing)
要回答您的问题,您根本没有处置,而是让终结器在需要时运行。
【讨论】:
WinForms 控件和组件实现了 IDisposable(因为它们确实拥有有限的资源,如 HWND 或计时器句柄),因此应该处置而不是等待终结者。 但是我应该担心保留引用并处理掉,还是让终结器来完成这项工作。保留引用也会增加类内存占用。 保留引用确实会增加几个字节的类内存占用。但是这个类是一个 Windows 窗体,对吗?除非您期望创建数十万个表单实例,否则一个额外引用的内存开销是微不足道的——只是不值得担心! 一个引用加起来,不必要也聚类类。您需要计时器、工具提示等的参考,然后您必须添加事件处理程序并将它们处置在这些事件处理程序中。开销对我来说看起来太大了,而不仅仅是强制 GC。 @itowlson:但是 HWND 的丢失也很微不足道。不像对它们有很大的限制。如果您很难跟踪何时需要处置一个对象,那么不要跟踪它,除非分析表明它会导致大量开销。至少通过使用任何跟踪系统,您都在复制 GC 为您所做的工作。更糟糕的是,你会通过双重工作和整体上对自己不利而损害绩效。假设对象 需要 因为有一个 Dispose() 方法而被释放,这有点过早的优化 IMO。【参考方案3】:在 .NET 中,当无法再从任何地方引用对象时,垃圾收集器将自动释放内存。当它这样做时,如果对象有一个终结器,它就会调用终结器。终结器用于清理,通常是非托管资源。
但是,终结器很昂贵(例如,它们会延迟对象上的垃圾收集)并且您无法确定它何时运行(因为您无法确定 GC 何时决定收集它)。它们通常作为清理非托管资源之类的最后手段。
这就是 IDisposable 接口及其 Dispose 方法的用武之地。Dispose 还可用于清理托管和非托管资源。当您调用 Dispose 方法时,它会清理并且对象不再处于稳定、可用的状态。
如果你有一个实现 IDisposable 的资源,你可以在知道你完成后调用 Dispose 。这样就可以尽快释放它所持有的资源。执行此操作的常用方法是将其包装在 using 语句中,该语句将在 using 块完成时自动 Dispose。例如:
using (SomeDisposableObject disposableObject = new SomeDisposableObject())
disposableObject.DoSomeStuff();
disposableObject.DoSomeMoreStuff();
当 using 块完成时(在 DoMoreStuff 之后)Dispose 在disposableObject 上调用。考虑到异常处理时,using 语句比等效代码更简洁。
对于像 ToolTip 这样的具有非托管引用(围绕非托管 Win32 组件的包装器中的许多 WinForms)的情况,它将有一个终结器,以确保正确释放非托管资源。但是,如果您在对象上调用 Dispose,则会在此处运行清理代码,然后抑制终结器(当对象被 GC 收集时它不会运行)。
因此,为了更直接地回答您的问题,如果您确切知道 IDisposable 何时完成,最好调用 Dispose。但是,如果您不这样做,通常只需将其留给垃圾收集器来收集并调用相关代码以正确清理对象即可。
在给出 ToolTip 和 Timer 的情况下,我不会担心自己对它们调用 Dispose,因为我怀疑很难预测它们何时会完成。
【讨论】:
这很有意义,不必要的簿记会使代码变得复杂,并在调用 GC 之前减少字节数。但是如何确保不需要手动处理对象,其他帖子中的人说您需要在表单相关对象上调用 Dispose,因为它们具有 HWND 等。 好吧,如果可以,请调用 Dispose 以清理 HWND 等。但是,如果你不这样做,终结器应该为你清理它。不确定这与意外应用程序退出等情况有何关系。希望更有资格的人能详细说明。【参考方案4】:您需要对任何实现 IDisposable 的东西调用 Dispose,或者至少确保某些东西可以实现。对于 UI 组件,将它们添加到表单的 Controls 集合中,它们将在表单关闭时被释放。对于其他事情,您需要保留对它们的引用并适当地调用 dispose。让你的类实现 IDisposable 只是为了在你的组合对象上调用 Dispose 可能是合适的。
【讨论】:
我不同意,为什么要保留不必要的参考,请参阅我在问题中的编辑。 如果您对最终确定期间释放的资源(垃圾收集时间)感到满意,则无需致电Dispose()
。
您保留(相对)便宜的参考资料,这样您就可以避免持有(相对)稀缺或昂贵的资源。【参考方案5】:
一般来说,注册到事件的对象要取消注册 - 不这样做会造成内存泄漏。您不应该依赖 Dispose 方法这样做。
Dispose 不会自动调用,除非您将对象包装在 using() 语句中,或者将其添加到对象 IContainer 实例中(如果它是设计器类)。如果是类变量,则需要使包含类实现 IDisposable 并在其中释放实例。
【讨论】:
你的回答不清楚,你的意思是说我需要保留对计时器的不必要引用,然后注销事件处理程序然后处置它? 是的 - 你需要保留一个引用,然后在事件被处理时取消注册 我猜.NET垃圾收集器会自动GC它,如果没有引用,它可能需要一些额外的时间,但调用Dispose不是强制性的,注册事件处理程序没有意义,并且在其中调用 dispose 并取消注册事件处理程序。以上是关于C#内存和dispose相关问题的主要内容,如果未能解决你的问题,请参考以下文章
C# IDisposable 类,正确使用 Dispose,获取错误“dispose”需要 1 个参数
关于C#的垃圾回收机制,Finalize和Dispose的区别(自认为很清晰了,有疑问的评论)
C#学习笔记---Dispose(),Finalize(),SuppressFinalize