单身人士与终结者但不是IDisposable

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

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

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

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

因此,在我看来,使用没有实现IDisposable的终结器的单例类似乎是一件合理的事情,但这种类型的设计与我所理解的最佳实践相反。

这是一种合理的方法吗? 如果没有,为什么不呢?什么是优越的选择?

如果非托管资源仅在应用程序退出时释放,您甚至不需要担心终结器,因为卸载过程应该为您处理此问题。

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

我是第二个说这个设计可能不是正确的事情(并且会让你更难修复,随后你发现你确实需要两个实例)在你的入口点创建对象(或一个延迟的加载包装器对象)并通过代码传递给需要的地方,明确谁负责将其提供给谁,然后你可以自由地改变决定只使用一个对其余代码影响不大的代码(使用它得到的东西)给出)

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

话虽这么说,我不确定我同意单例实例只应在应用程序结束时处理的前提。 我为Singleton (或设计模式:可重用的面向对象软件的元素)阅读的设计模式描述中没有任何内容提到这是该模式的属性。 单身人员应该确保在任何一个时刻只存在一个类的实例; 这并不意味着只要应用程序存在就必须存在。

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

换句话说,您可以创建场景,使单身人士具有IDisposable是合乎逻辑的。

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

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

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

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

虽然它可能会让你获得代码审查抱怨和FxCop警告,但实现没有IDisposable的终结器没有任何本质上的错误。 但是,在单例上执行此操作并不是捕获进程或AppDomain拆除的可靠方法。

冒着提供主观设计建议的风险:如果对象是真正无状态的,那就把它变成静态类。 如果它是有状态的,那就问为什么它是一个Singleton:你正在创建一个可变的全局变量。 如果您正在尝试捕获应用程序,请在主循环退出时处理它。

Singleton对任何特定情况的适用性除外,

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

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