【问题标题】:Manually destroy C# objects手动销毁 C# 对象
【发布时间】:2010-12-31 12:43:08
【问题描述】:

我对学习 C#(来自 Java 和 C++ 背景)相当陌生,并且我有一个关于手动垃圾处理的问题:甚至可以在 C# 中手动销毁对象吗?我知道IDisposable 接口,但假设我正在处理一个我没有编写并且它没有实现它的类?它不会有 .Dispose() 方法,因此 using { } 已退出,.Finalize 始终是 protectedprivate,所以这也不是一个选项。

(在这种情况下,我只是想了解 C# 中的可能。我想如果所有其他方法都失败了,我可以继承假设的 ImNotDisposable 类,以便它确实实现了 IDisposable。)

【问题讨论】:

  • 也许你应该澄清这个问题:你想完全释放一个对象,还是只强制执行它的析构函数并清理对象的资源(只)?
  • 我认为一个暗示了另一个,但我想到的是某种方式来“触发”一个对象具有的 ~ClassName() 方法。
  • 所以总而言之,GC 有 Collect() 方法,它几乎是全有或全无,而不是针对特定对象的任何方法。明白了。 :)

标签: c# destructor idisposable using


【解决方案1】:

您不会手动销毁 .Net 对象。这就是托管环境的全部意义所在。

事实上,如果对象实际上是可访问的,这意味着您有一个引用可以用来告诉 GC 您要销毁哪个对象,那么收集该对象将是不可能的。 GC 将从不收集任何仍可访问的对象。

您可以调用GC.Collect() 强制进行常规收集。然而,这几乎从来都不是一个好主意。

相反,最好假装任何不使用非托管资源且程序中任何其他对象都无法访问的对象立即被销毁。我知道这不会发生,但此时对象只是一块内存,就像其他任何东西一样;你无法回收它,它最终会被收集起来,所以它可能对你来说已经死了。

关于IDisposable 的最后一点说明。您应该只将它用于包装非托管资源的类型:诸如套接字、数据库连接、gdi 对象等,以及偶尔的事件/委托订阅。

【讨论】:

  • 我还使用 dispose 取消与外部对象关联的任何事件的链接。如果您不这样做,则在外部对象存在之前,该对象不会从内存中删除,这可能是在应用程序的生命周期内。
  • @MongusPong: Dispose 非常重要,但它不会“破坏”对象——而是通知他们不再需要他们的服务,以便其他实体(代表他们行事(如果有的话)可以依次被通知他们的服务不再需要。
  • 我完全不同意“你应该只将它用于包装非托管资源的类型”的一般说法。我知道这是传统智慧,但如果您遇到内存泄漏问题,将项目设为一次性是非常好的。原因是良好的内存跟踪应用程序将能够向您显示已释放但尚未销毁的对象。它清楚地向您显示您已标记为销毁但尚未销毁的内容。除此之外,它还为您提供了一个很好的机会和标准位置来解开事件处理程序等,以便正确破坏网格。
  • “关于 IDisposable 的最后一点说明。您应该只将它用于包装非托管资源的类型:如套接字、数据库连接、gdi 对象等,以及偶尔的事件/委托订阅。”并且,对于实现 IDisposible 且被相关类使用或继承的 C# 类。把它传下去。
【解决方案2】:

如果无法访问该对象,那么您可以调用GC.Collect() 并且该对象将被销毁。 IDisposable 的概念与 CLR 无关,主要是供用户代码实现以执行附加处理逻辑。对对象调用 Dispose() 不会从内存中释放对象本身,尽管它很可能会释放该对象引用的任何资源。

我应该补充一点,虽然我所说的是实现此目的的一种方法,但在 99.9999% 的应用程序中,您永远不应该调用 GC.Collect(),因为它通常会降低应用程序的性能,而不是改进它。

【讨论】:

  • 致电GC.Collect() 不是您应该做的事情,除非是出于非常充分的理由。
  • 这与对对象的引用无关 - 它与可达性有关(我确实提到过)。
  • 如果对象 a 引用了对象 b,而 b 引用了 a,则 a 和 b 都被引用(也称为循环引用)。但是,如果没有其他人引用 a 或 b,则两者都不可达,垃圾收集器将销毁它们(最终)。
  • 时间对 GC 性能没有影响。这完全取决于分配内存的频率和数量。将调用间隔几分钟这一事实毫无意义 - 如果不会发生正常诱导的 GC,那么您可能会不必要地将对象提升到影响性能的更高代。特别是在长时间运行的应用程序和服务中。
  • @Cecil:是的,它会影响性能。想象一下强制你的主线程停止收集,即使没有什么可以收集,或者当你的应用程序繁忙时强制收集。更糟糕的是,因为垃圾收集器是分代的,你可以强制对象移动到真正不应该这样做的更长寿的一代中。
【解决方案3】:

虽然您可以触发垃圾回收(您需要为所有代触发 GC,因为您无法确定可终结对象在哪一代),但您不一定强制对特定对象进行终结。您只能依赖关于垃圾收集器如何工作的假设。

Furtnermore,由于终结发生在它自己的线程上,你应该在触发垃圾回收后调用WaitForPendingFinalizers

GC.Collect(GC.MaxGeneration);
GC.WaitForPendingFinalizers();

正如其他人所指出的那样,这实际上会损害您的应用程序的性能,因为不必要地调用 GC 可能会将原本寿命较短的对象提升到更高的世代,这些对象的收集成本更高且收集频率更低。

一般来说,实现终结器(析构函数)但不实现 IDisposable 的类是不受欢迎的。任何实现 IDisposable 的东西都应该调用它的终结器逻辑,并在垃圾回收时抑制自己的终结。

Jeff Richter 最近为receiving a notification when garbage collection occurs 发布了一个不错的小技巧。

Rico Mariani (MSFT) 关于Garbage Collector Basics and Performance Hints 的另一篇精彩文章

【讨论】:

    【解决方案4】:

    不,您不能销毁特定对象。

    可以调用垃圾收集器,它会寻找要销毁的对象,但这几乎不是一个好主意。

    【讨论】:

      【解决方案5】:

      可以在您要销毁的变量超出范围后强制垃圾收集器运行,但您通常不希望这样做,因为如果离开,垃圾收集器效率更高做自己的工作。

      可以使用GC.Collect 进行强制垃圾回收,但不要这样做。在我作为 .NET 开发人员的 10 年中,我从来不需要它。

      【讨论】:

        【解决方案6】:

        不能“像 C++ 删除”那样手动销毁一个对象,你所能做的就是关闭该对象已获取的所有独占资源并将对该对象的所有引用设为空,因此 GC可以收集它,也不要自己调用 GC.Collect(),GC 过程很昂贵,因为它必须暂停所有其他线程才能安全地从内存中收集对象,所以只要相信 GC,它就会启动需要时。

        【讨论】:

        • 好吧,我实际上不会将任何引用变量分配给像 myObj = null; 这样的空值。我只会让他们超出范围。
        • 我同意,如果可能的话,我宁愿尝试这样做
        【解决方案7】:

        不可能以确定的方式销毁对象。 CLR 确定何时回收标记的对象。这意味着虽然您可以标记要回收的对象并确保整理托管和非托管资源以进行处置(通过实现 IDisposable 模式),但释放内存的实际时间取决于 CLR。这与 C++ 不同,在 C++ 中您可以实际删除某些内容,然后将其释放。

        【讨论】:

        • 调用 dispose 不会将对象标记为可回收的,它只是允许您在完成数据库连接后立即释放有限的资源,而不是等待垃圾收集器来决定是时候完成对象了。
        【解决方案8】:

        假设您有一个类矩阵,并创建了两个矩阵对象aMatrixbMatrix。在 C# 中,您可以像这样手动销毁(最终确定)一个对象:

        aMatrix = null;
        
        GC.Collect();
        

        垃圾收集器会注意到您的aMatrix 为空,并将销毁(最终确定)它。这是否是一个好主意是另一回事。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2023-02-08
          • 1970-01-01
          • 2011-09-18
          • 1970-01-01
          • 2020-04-06
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多