手动销毁C#对象

44

我相对于Java和C++比较新学习C#,现在有一个关于手动垃圾回收的问题:在C#中是否可以手动销毁一个对象呢? 我知道IDisposable接口,但是假设我正在处理一个我没有编写并且没有实现IDisposable接口的类呢?它将没有.Dispose()方法,所以using { }也不可用,而.Finalize始终是protectedprivate,因此这也不是一个选择。

(我只是想了解在这种情况下C#中可能发生的事情。我想如果万不得已,我可以继承这个假设的ImNotDisposable类,使其实现IDisposable接口。)


4
也许你应该澄清这个问题:你是想彻底释放一个对象,还是只强制执行它的析构函数并清理该对象的资源(仅此而已)? - David R Tribble
我以为一个意味着另一个,但我所想的是一种触发对象具有的~ClassName()方法的方式。 - East of Nowhere
因此,总的来说,GC有一个Collect()方法,基本上是全有或全无,并没有任何方式可以针对一个特定的对象。注意了。 :) - East of Nowhere
8个回答

38

您不需要手动销毁 .Net 对象。这正是受控环境的全部含义。

实际上,如果对象实际上是可达的,意味着您有一个引用可以告诉 GC 您想要销毁哪个对象,那么收集该对象将会变得不可能。GC 永远不会 收集任何仍然可达的对象。

您可以调用 GC.Collect() 强制执行一次垃圾回收。然而,这几乎从来都不是一个好主意。

相反,最好的方法可能只是假装任何不使用非托管资源且不被程序中任何其他对象引用的对象立即被销毁。我知道这并不会真正发生,但此时该对象就像任何其他内存块一样;您无法回收它,它最终将被收集,因此对您而言它可能已经失效了。

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


4
我还使用dispose方法来取消连接到外部对象的任何事件。如果不这样做,该对象将一直保存在内存中,直到外部对象被清除为止,这可能是整个应用程序的生命周期。 - Mongus Pong
1
@MongusPong:Dispose非常重要,但它并不会“销毁”对象——相反,它会通知它们不再需要它们的服务,以便其他实体(如果存在)代表它们被通知,他们的服务也不再需要。 - supercat
1
我完全不同意“你只应该将其用于包装非托管资源的类型”的一般说法。我知道这是传统智慧,但如果你遇到了内存泄漏问题,让项目可处置是非常好的。原因是良好的内存跟踪应用程序将能够向你显示已被处理但尚未被销毁的对象。它清楚地显示您已标记为销毁但尚未被销毁的内容。除此之外,它还为您提供了一个很好的机会和标准位置来取消挂载事件处理程序等,以便网格被正确断开连接。 - Christian Findlay
关于IDisposable的最后一点说明。您应该仅将其用于包装非托管资源的类型:例如套接字、数据库连接、gdi对象等,以及偶尔的事件/委托订阅。对于实现IDisposible的C#类,如果它们被正在使用或继承的类使用,请将其传递下去。 - BoiseBaked

10
尽管您可以触发垃圾回收(需要触发所有代的GC,因为您无法确定可终结对象在哪个代中),但不能保证强制终结特定对象。您只能依赖于关于垃圾收集器工作方式的假设。
此外,由于终结是在其自己的线程上发生的,因此在触发垃圾回收后应调用WaitForPendingFinalizers
GC.Collect(GC.MaxGeneration);
GC.WaitForPendingFinalizers();

正如其他人所指出的那样,这实际上会损害您的应用程序性能,因为不必要地调用垃圾回收器可能会将本来短暂的对象提升到更高的代中,这些代更难以收集并且收集频率更低。
一般来说,实现终结器(析构函数)但未实现IDisposable接口的类是不被赞同的。而任何实现IDisposable接口的内容都应该在垃圾回收时调用其终止逻辑并抑制自身的终止。
Jeff Richter最近发布了一个很好的小技巧,可以在垃圾回收发生时接收通知
另一篇关于垃圾收集器基础和性能提示的优秀文章由Rico Mariani(MSFT)撰写。

1
这实际上在测试代码中触发了析构函数方法。谢谢。我想检查具有多重继承的析构函数流程。正如预期的那样,它是从子类开始的。 - Royston dsouza

9
如果对象不可到达,则可以调用GC.Collect()来销毁对象。概念IDisposable与CLR无关,主要是为了用户代码实现执行附加处理逻辑。调用Dispose()方法不会释放对象本身的内存,但可能会释放此对象引用的任何资源。
我应该补充说明,虽然我所说的是一种实现方式,但在99.9999%的应用程序中,您永远不应该调用GC.Collect(),因为它通常会降低应用程序的性能而不是提高它。

4
调用 GC.Collect() 不是你应该做的事情,除非有一个非常好的理由。 - Sander Rijken
2
重点不在于对象的引用,而在于可达性(我确实提到了这一点)。 - Eilon
4
如果对象a引用对象b,而b也引用a,则a和b都被引用(也称为循环引用)。然而,如果没有其他人引用a或b,那么它们都无法访问,垃圾回收器将销毁它们(最终)。 - Eilon
2
时间在GC性能中并不起任何作用。重要的是内存分配的频率和数量。如果通常引发的GC没有发生,那么你每隔几分钟调用一次也毫无意义 - 这可能会不必要地将对象提升到更高的代中,从而影响性能。特别是在长时间运行的应用程序和服务中。 - Josh
4
@Cecil:是的,它会影响性能。想象一下,即使没有要清除的对象,也强制主线程停止进行垃圾回收,或者在应用程序繁忙时强制进行回收。更糟糕的是,因为垃圾回收器是分代的,你可能迫使对象转移到生命周期更长的代中,而这些对象其实不应该这样做。 - Joel Coehoorn
显示剩余4条评论

7

不可以销毁特定的对象。

可以调用垃圾回收器,对需要销毁的对象进行处理,但这几乎从来不是一个好主意。


5

当你想要销毁的变量超出范围后,你可以强制垃圾回收器运行,但通常不建议这样做,因为如果让垃圾回收器自己工作,它会更有效率。

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


3

不能像C++中的delete一样手动销毁对象,你所能做的仅仅是关闭对象已获取的任何独占资源并将所有对该对象的引用设置为null,以便GC可以收集它。同时,不要自己调用GC.Collect(),因为GC过程很昂贵,必须暂停所有其他线程以安全地从内存中收集对象,所以请相信GC,它会在需要时启动。


5
我不会将任何引用变量分配给 null 值,例如 myObj = null;我只会让它们超出作用域。 - East of Nowhere
2
我同意,如果可能的话,我宁愿尝试做这件事。 - bashmohandes

2

无法以确定性方式销毁对象。CLR决定何时回收标记的对象。这意味着,虽然您可以标记一个对象进行回收,并确保实现IDisposable模式以清理托管和非托管资源以进行处理,但实际释放内存的时间取决于CLR。这与C ++不同,您可以实际删除某些内容并在那时释放它。


2
调用dispose方法并不会标记对象为可回收,它只是允许您在完成使用后释放有限的资源,例如数据库连接,而不必等待垃圾回收器来决定何时对对象进行终结。 - Paolo

1

假设您有一个类矩阵,并创建了两个矩阵对象aMatrixbMatrix。在C#中,您可以手动销毁(终止)对象,如下所示:

aMatrix = null;

GC.Collect();

垃圾收集器会注意到你的aMatrix是空的,并销毁(终结)它。无论这是否是一个好主意,都是另一回事。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接