16

这是我从“CLR via C#”、“Effective C#”和其他资源中对 IDisposable 和终结器的理解:

  • IDisposable 用于确定性地清理托管和非托管资源。
  • 负责非托管资源(例如文件句柄)的类应实现 IDisposable 并提供终结器以确保即使客户端代码未在实例上调用 Dispose() 也能清理它们。
  • 只负责托管资源的类不应该实现终结器。
  • 如果你有一个终结器,那么你必须实现 IDisposable(这允许客户端代码做正确的事情并调用 Dispose(),而终结器可以防止在他们忘记时泄漏资源)。

虽然我理解并同意上述所有内容,但在一种情况下,我认为打破这些规则是有意义的:一个负责非托管资源的单例类(例如提供对特定文件的单点访问) )。

我认为在单例上使用 Dispose() 方法总是错误的,因为单例实例应该在应用程序的整个生命周期内都存在,如果有任何客户端代码调用 Dispose() 那么你就被塞满了。但是,您需要一个终结器,以便在卸载应用程序时终结器可以清理非托管资源。

因此,在我看来,拥有一个带有不实现 IDisposable 的终结器的单例类似乎是一件合理的事情,但这种类型的设计与我所理解的最佳实践背道而驰。

这是一个合理的方法吗?如果不是,为什么不呢?有什么更好的选择?

4

6 回答 6

6

我首先要提到面向对象的设计模式及其后果并不总是影响每个语言决策,即使在面向对象的语言中也是如此。您当然可以找到用一种语言(Smalltalk)而不是另一种语言(C++)更容易实现的经典设计模式。

话虽如此,我不确定我是否同意单例实例只应在应用程序结束时处理的前提。我读过的Singleton(或Design Patterns: Elements of reusable Object-Oriented Software)的设计模式描述中没有提到这是该模式的属性。单例应该确保在任何时刻只存在一个类的实例;这并不意味着只要应用程序存在,它就必须存在。

我有一种感觉,在实践中,在应用程序的大部分生命周期中确实存在许多单例。但是,考虑使用 TCP 连接与服务器进行通信的应用程序,但也可以以断开模式存在。连接后,您可能需要一个单例来维护连接信息和连接状态。断开连接后,您可能希望保留相同的单例 - 或者您可以处理该单例。虽然有些人可能认为保留单例更有意义(我什至可能是其中之一),但设计模式本身并没有阻止您处理它 - 如果重新建立连接,则可以实例化单例再次,因为在那个时刻不存在它的实例。

换句话说,您可以创建单例具有 IDisposable 的逻辑场景。

于 2009-01-21T00:49:07.757 回答
5

如果非托管资源仅在应用程序退出时释放,您甚至无需担心终结器,因为进程卸载无论如何都应该为您处理这个问题。

如果您有多个应用程序域并且您想要处理一个应用程序域卸载,这是一个可能的问题,但可能是您不需要关心的一个问题。

我赞同那些说这种设计可能不是正确的做法(并且会使其更难修复,因为随后您发现您确实需要两个实例)在您的入口点创建对象(或延迟加载包装器对象)并将其通过代码传递到需要的地方,明确谁负责将其提供给谁,然后您可以自由更改决定只使用一个对其余代码几乎没有影响的决定(它使用它得到的给定)

于 2009-01-21T12:59:39.047 回答
4

只要您的终结器不调用任何其他托管对象的方法(例如 Dispose),您就应该没问题。请记住,最终确定顺序不是确定性的。也就是说,如果您的单例对象 Foo 包含对需要处理的对象 Bar 的引用,则您无法可靠地编写:

~Foo()
{
    Bar.Dispose();
}

垃圾收集器可能已经收集了 Bar。

冒着陷入一堆OO goo(即开始战争)的风险,使用单例的一种替代方法是使用静态类。

于 2009-01-21T00:49:31.313 回答
2

虽然它可能会让您对代码审查感到不满和 FxCop 警告,但在没有 IDisposable 的情况下实现终结器并没有本质上的错误。但是,对单例执行此操作并不是捕获进程或 AppDomain 拆除的可靠方法。

冒着提供主观设计建议的风险:如果对象确实是无状态的,则将其设为静态类。如果它是有状态的,请质疑它为什么是单例:您正在创建一个可变的全局变量。如果您试图捕获应用程序关闭,请在主循环退出时处理它。

于 2009-01-21T01:07:29.497 回答
0

除了单例对任何特定情况的适用性,

我认为 Disposing of Singleton 没有任何问题。结合惰性实例化,它只是意味着如果您暂时不需要它,则释放资源,然后根据需要重新获取它。

于 2009-01-21T13:47:32.990 回答
0

如果你想创建一个带有终结器的单例,你可能应该将它的静态引用设为 WeakReference。这将需要一些额外的工作来确保访问器中的线程安全,但是它将允许在没有人使用它时对单例进行垃圾收集(如果有人随后调用 GetInstance() 方法,他们将得到一个新实例)。如果使用静态强引用,即使没有其他对它的引用,它也会使单例实例保持活动状态。

于 2010-09-27T23:06:32.977 回答