在单例模式中使用Dispose清理资源

8
我有一个问题,可能与语义有关,而不是与实际使用IDisposable有关。我正在实现一个单例类,负责管理在应用程序执行期间创建的数据库实例。当应用程序关闭时,应该删除此数据库。
目前,我通过单例的Cleanup()方法处理这个删除,应用程序在关闭时调用它。当我为Cleanup()编写文档时,我意识到我正在描述Dispose()方法应该用于清理资源的内容。最初,我没有实现IDisposable,因为它似乎在我的单例中不合适,因为我不想让任何东西处理单例本身的释放。目前并没有,但将来可能会有一个原因,即需要调用Cleanup(),但单例仍需要存在。我认为我可以在Dispose方法中包含GC.SuppressFinalize(this);来使这成为可能。
因此,我的问题有多个部分:
1)在单例上实现IDisposable基本上是一个坏主意吗?
2)我是否仅仅是混淆了语义,通过使用Cleanup()而不是Dispose(),而且由于我正在处理资源,我真的应该使用Dispose()吗?
3)使用GC.SuppressFinalize(this)实现Dispose()会使得我的单例在清理数据库后实际上没有被销毁吗?
3个回答

10
  1. 如果您使用CAS技术而非锁来创建单例,那么为单例实现IDisposable可能是一个好主意。可以像这样实现:

if (instance == null) {
    var temp = new Singleton();
    if (Interlocked.CompareExchange(ref instance, temp, null) != null) &&
            temp is IDisposable) {
        ((IDisposable)temp).Dispose();
    }
}
return instance
我们创建了一个临时对象并尝试原子比较和交换,因此如果它实现了IDisposable接口且未写入到实例位置,则需要处理此临时对象。
如果在构造函数中使用了复杂的逻辑来创建单例对象,则避免锁可能是不错的选择,但这也可能会增加一些开销。
如果您不希望其他代码清理或处理您的对象,只需不提供任何这样的机会即可。然而,提供某种reset()方法以允许单例重新创建自己可能是个好主意,比如您使用了懒惰初始化。就像这样:
public static Singletong GetInstance() {
  if (instance == null) {
    instance = new Singleton(); //here we re-evalute cache for example
   }
return instance
}
public static void Reset() {
    instance = null;
} 

1
只是想指出第二个示例不是线程安全的,如果您不需要能够重新创建单例实例,则最好的方法是在可能的情况下在静态变量声明的位置初始化它。 - haze4real

8
简单来说,如果你有一个单例并调用了dispose方法,在之后任何对象尝试使用它时,都会使用处于已释放状态的对象。
现在将其放入,并在应用程序完成后处理该对象并不一定是不好的。但是当你调用它时,必须小心。如果你真的关心清理工作,并且只有一个引用它,你可以将清理代码放在对象的finalizer中 ~YourClass 这样它只会在.Net确保不再需要它时才被调用(如果它是一个真正的singleton,则在应用程序关闭时)。

我是否仅仅因为有一个Cleanup()而不是Dispose()而混淆了语义,而且由于我正在处理资源,所以我应该使用dispose?

是的,这只是语义问题。Dispose是展示程序结束时有需要清理的东西的标准方式。

通过实现'Dispose()'和GC.SuppressFinalize(this);,我的singleton是否不会在我想要清除数据库后继续存在。

不,这意味着当你调用dispose方法时,垃圾回收器不会调用对象的自定义finalizer。

你会在应用程序的退出点调用单例模式的 .Dispose() 吗? - LuckyLikey
请记住,Dispose 方法与 using 语句绑定,该语句会自动调用它。但对于自定义的 Cleanup(或任何其他)方法则不是这样。 - Mike Lowery

1
我同意Kevin的回答,但是想要补充一些内容。我对你的陈述有点困惑:
当应用程序关闭时,这个数据库应该被删除。
你真的是指删除吗?就像摧毁一样?你是在谈论一个真正的(SQL)数据库吗?
你必须明白,即使你把清理代码放在finalizer中,或者在Application_End事件(ASP.NET)中,也无法保证这些代码会被调用。进程可能会被终止,或者计算机失去电源。似乎更合理的做法是在应用程序启动时删除数据库,或者至少在启动时有一个备用机制进行一些清理。
虽然finalizer是处理资源清理的好地方,但在你的情况下,我们正在谈论一个应用程序资源。我的意思是,这个资源可能不仅仅与一个对象(你的单例)有关,而是整个应用程序的一部分。这可能会变成一个抽象的讨论,更多的是一种观点问题。
我想说的是,当您将数据库视为应用程序资源时,您必须将初始化和清理与应用程序绑定,而不是与对象绑定。在ASP.NET应用程序中,这将是Application_Start和Application_End(global.asax)。在Windows Forms应用程序中,这将是Program.Main。
然而,使用这些机制而不是使用finalizers时,您无法确定您的清理代码是否会执行。

数据库是应用程序使用的嵌入式数据库,用于保持其运行过程的状态。它可能会变得非常大。如果由于停电或其他原因而没有被删除,那么用户可以从文件系统中删除该文件。此外,它并不总是被删除,因为用户可以选择在应用程序关闭后继续保留它。我的单例对象负责创建和删除数据库(在应用程序的整个生命周期中可能有多个),因此我相信它应该负责在关闭时进行删除。 - Craig Suchanec

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