这是对GC.ReRegisterForFinalize()的错误使用吗?

本文关键字:错误 GC ReRegisterForFinalize | 更新日期: 2023-09-27 18:18:04

所以我听说一般应该避免使用GC.ReRegisterForFinalize(),但我认为没有理由不使用它来解决我的特定问题。首先,这是一个对性能非常敏感的代码区域,所以速度很重要。

我有一堆非托管资源和小的(8字节)托管对象,每个对象都包装了这些非托管资源中的一个。当创建包装器时,它将非托管资源推入堆栈并包装它。当它被销毁而不是从堆栈中间移除它的资源(这会导致整个堆栈移动)时,它可以做两件事之一:它可以将一个表示其堆栈位置的int压入队列,这样下一个创建的对象可以包装该堆栈位置,而不是压入一个新的堆栈位置,或者它可以将自己压入队列并重新注册以完成并被重用。

差异可能很小,我意识到我可能是在吹毛求疵,但至少在第二种方法中,我节省了每次分配和释放的时间。那么缺点是什么呢?

这是对GC.ReRegisterForFinalize()的错误使用吗?

好的,让我把这个作为答案写下来。

GC.ReRegisterForFinalize有两个用途-恢复GC.SuppressFinalize()方法,并"复活"一个已经在它的终结器(~SomeType方法)中的对象。

你只需要理解如何完成。其基本思想是,持有一些非托管资源的托管对象在被垃圾收集器收集时将有机会释放非托管资源。由于在大多数情况下,您希望更早地释放资源(例如,释放SqlConnection以确保我们没有保持不必要的套接字打开),因此引入了一种优化,这是"一次性模式"的一部分。

因此,终结器将包含用于摆脱非托管资源的代码,而不包含其他代码。为了处理早期的处理,我们将实现一个Dispose方法,它做一些清理工作,包括释放非托管资源。现在,类型确实有一个终结器,所以默认情况下它是为终结而注册的——然而,我们之前已经释放了那些非托管资源,所以在集合上进行终结没有意义。所以你调用GC.SuppressFinalize(this),这基本上是从项目列表中删除这个实例来完成收集。

但是,如果您的类不仅仅是非托管资源的简单包装器,而是允许您再次释放和重新获取它,该怎么办?GC.ReRegisterForFinalize来拯救-每当您重新获得非托管资源时,您将再次注册自己以进行终结,以确保正确的清理。

这在某种程度上违反了。net中处理未修改资源的一个简单原则,即托管包装器应该尽可能小而简单。因此,执行一些实际工作的实际类将使用这个小包装器,而不是直接使用非托管资源。这就是为什么你真的不需要GC.ReRegisterForFinalize的原因之一,也是为什么在代码中看到它可能看起来很可疑的原因之一。

GC.ReRegisterForFinalize的第二次使用更加令人反感。它允许您停止实例的终结,并使其恢复正常。听起来不太糟吗?首先,它表明你的设计可能有缺陷。更重要的是,所有其他仅由这个实例引用的实例(例如,指向另一个。net对象的字段)可能已经被收集了。这是引入难以复制的bug的好方法。程序员不太喜欢这些,所以你应该尽量避免这些。

所以,TL;DR版本:你可能不需要GC.ReRegisterForFinalize。如果您将托管包装器保存在"未使用"对象列表中,那么它将不会被收集,因此它也不会被最终确定。只有当您首先释放非托管资源(在Dispose中包括对GC.SuppressFinalize的调用),然后想要为同一个托管实例创建一个新的非托管资源时,才会有所不同。这可能不是你想要的(这真的没有多大意义)。