3

我编写了一个将 aTransactionScope与 Linq to Sql配对的类DataContext

它实现了与 , 相同的方法TransactionScopeDispose()并且Complete()公开了 DataContext。

其目的是确保 DataContexts 不会被重复使用,它们与单个事务配对并与它一起被处置。

我应该在类中包含一个 Finalize 方法吗?如果尚未调用 Dispose,则调用它?还是仅适用于引用非托管资源的 IDisposables?

4

3 回答 3

3

终结器仅用于清理非托管资源。在终结器中调用依赖对象的处置是没有用的,因为如果这些对象管理关键资源,它们本身就有终结器。

在 .NET 2.0 及更高版本中,实现终结器的理由更少,没有 .NET 包含SafeHandle类。

然而,我有时会发现仍然实现终结器的一个原因是要找出开发人员是否忘记调用 Dispose。我让这个类只在调试构建中实现终结器,并让它写入调试窗口。

于 2012-08-15T08:34:10.130 回答
3

不,永远不要在一次性类中实现终结器,因为它包装了一次性类。

假设您有一个带有终结器的类的三个清理场景Dispose

  1. Dispose()叫做。
  2. 在应用程序关闭时调用终结器。
  3. 该对象将被收集,但终结器没有被抑制(通常来自对 的调用Dispose(),但请注意,当任何事情使其处于不需要清理的状态时,您应该始终抑制终结器, 并在它处于确实需要它的状态时重新注册 - 例如,如果你有Open()/Close()对方法)。

现在,如果您直接管理非托管资源(例如,通过 的句柄IntPtr),这三个场景中您将直接调用两个清理方法之一,这与您需要进行清理的三个场景相匹配。

好的。所以,让我们考虑一个一次性包装一次性用品,其中“外部”类有一个正确实现的终结器:

~MyClass()
{
  // This space deliberately left blank.
}

finaliser 不做任何事情,因为没有非托管清理可供它处理。唯一的效果是,如果这个 null 终结器没有被抑制,那么在垃圾回收时,它将被放入终结器队列 - 保持它以及只能通过它的字段访问的任何东西,并将它们提升到下一代 - 最终完成线程将调用此 nop 方法,将其标记为已完成,并再次符合垃圾回收条件。但是自从它被推广以来,如果它是 Gen 0,它将是 Gen 1,如果它是 Gen 1,它将是 Gen 2。

确实需要最终确定的实际对象也将被提升,它不仅要等待更长时间,还要等待最终确定。无论如何,它将最终出现在第 2 代。

好吧,这已经够糟糕了,假设我们实际上在终结器中放入了一些代码,这些代码对包含可终结类的字段做了一些事情。

等待。我们会做什么?我们不能直接调用finaliser,所以我们dispose它。哦等等,我们确定这个类的行为Dispose()和终结者的行为足够接近它是安全的吗?我们怎么知道它不会通过弱引用保留一些资源,它将尝试在 dispose 方法中而不是在 finaliser 中处理?该类的作者完全了解在终结器中处理弱引用的危险,但他们认为他们正在编写一个Dispose()方法,而不是其他人的终结器方法的一部分。

然后,如果在应用程序关闭期间调用了外部终结器怎么办。你怎么知道内部终结器还没有被调用?当类的作者在编写他们的Dispose()方法时,他们肯定会思考“好吧,现在让我们确保我处理这个在终结器已经运行之后被调用的情况,并且这个对象唯一剩下要做的就是拥有它内存释放了吗?” 并不真地。可能是他们提防反复呼吁Dispose()以这种方式也可以避免这种情况,但你不能真正打赌(特别是因为他们不会在终结器中提供帮助,他们知道这将是最后一个调用的方法以及任何其他类型的清理,比如将不会再次用于标记它们的空字段是没有意义的)。它最终可能会做一些事情,比如删除一些引用计数资源的引用计数,或者违反它的工作要处理的非托管代码的合同。

所以。在此类中使用终结器的最佳情况是您破坏了垃圾收集的效率,更糟糕的是您的错误会干扰您试图帮助的完美清理代码。

还要注意模式背后的逻辑 MS 用来提升(并且仍然在他们的一些类中)你有一个受保护的Dispose(bool disposing)方法。现在,我对这个模式有很多不好的地方要说,但是当你看到它时,它旨在处理这样一个事实,即你清理的Dispose()内容和你在 finaliser 中清理的内容是不一样的——该模式意味着在这两种情况下都将清除对象的直接持有的非托管资源(在您的问题的场景中,没有此类资源),并且仅从IDisposable以下位置清除托管资源(例如内部持有的对象),而不是来自终结者。Dispose()

IDisposable.Dispose()如果您有任何需要清理的东西,无论是非托管资源、对象IDisposable还是其他任何东西,请实施。

当且仅当您直接拥有需要清理的非托管资源时才编写终结器,并使清理它成为您在那里唯一要做的事情。

对于奖励积分,避免同时在两个类中 - 将所有非托管资源包装在仅处理该非托管类的一次性和可终结类中,如果您需要将该功能与其他资源组合,请在使用的一次性类中执行这样的课程。这样清理会更清晰、更简单、更不容易出现错误,并且对 GC 效率的损害更小(没有一个对象的最终确定延迟另一个对象的风险)。

于 2012-08-15T09:56:21.860 回答
1

这个问题没有简单的答案——这是值得商榷的。

什么?

争论是是否使用具有完整Disposable模式的终结器。您的代码使用事务和数据库上下文 - 那些人(通常)使用非托管资源(如内核事务对象和 TCP/IP 连接)

为什么?

如果您使用任何应该清理的非托管资源,您应该实现IDisposable. 然后客户端代码可以将对类的调用包装到推荐的using(IDisposable myClass = new MyClass(){...}构造中。问题是如果开发人员不会IDisposable.Dispose()显式或隐式调用,那么资源就不会自动释放。即使对象myClass已被 GC 收集。这是因为 GCDispose在收集期间从不调用,这是完成队列的责任。

因此,您可以定义一个最终将由 GC finilisation 线程调用的 finiliser,它独立于垃圾收集。

意见

有些人认为,您应该确保将所有一次性代码放入using (){}其中而忘记完成。毕竟你必须尽快释放这些资源,而且整个完成过程对于许多开发人员来说有点模糊。

相比之下,我更喜欢显式实现 finilisator,只是因为我不知道谁会使用我的代码。因此,如果有人忘记在需要它的类上调用 a Dispose,资源最终将被释放。

结论

就个人而言,我建议使用任何实现IDisposable.

于 2012-08-15T08:34:55.480 回答