C# 带有终结器但不可IDisposable的Singleton

C# 带有终结器但不可IDisposable的Singleton,c#,.net,singleton,idisposable,C#,.net,Singleton,Idisposable,以下是我从“CLR via C#”和“Effective C#”以及其他资源中对IDisposable和finalizer的理解: IDisposable用于决定性地清理托管和非托管资源 负责非托管资源(例如文件句柄)的类应实现IDisposable,并提供终结器,以确保即使客户端代码未在实例上调用Dispose(),它们也会被清除 仅负责托管资源的类不应实现终结器 如果您有一个终结器,那么您必须实现IDisposable(这允许客户端代码做正确的事情并调用Dispose(),而终结器可以防止

以下是我从“CLR via C#”和“Effective C#”以及其他资源中对IDisposable和finalizer的理解:

  • IDisposable用于决定性地清理托管和非托管资源
  • 负责非托管资源(例如文件句柄)的类应实现IDisposable,并提供终结器,以确保即使客户端代码未在实例上调用Dispose(),它们也会被清除
  • 仅负责托管资源的类不应实现终结器
  • 如果您有一个终结器,那么您必须实现IDisposable(这允许客户端代码做正确的事情并调用Dispose(),而终结器可以防止资源在忘记时泄漏)
虽然我理解并同意上述所有内容,但有一种情况下,我认为打破这些规则是有意义的:一个负责非托管资源(例如提供对特定文件的单点访问)的单例类

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

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


这是否合理的做法?如果不是,为什么不呢?还有什么更好的选择?

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

也就是说,我不确定我是否同意这样一个前提,即单例实例应该只在应用程序结束时处理。我在阅读的设计模式描述中没有提到(或)将此作为此模式的属性。单例应该确保在任何时刻只存在类的一个实例;这并不意味着只要应用程序存在,它就必须存在

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


换句话说,您可以创建这样的场景:单例具有IDisposable是合乎逻辑的。

只要您的终结器不调用任何其他托管对象上的方法(如Dispose),您就可以了。请记住,定稿顺序不是确定的。也就是说,如果您的单例对象Foo持有对需要处理的对象栏的引用,则无法可靠地写入:

~Foo()
{
    Bar.Dispose();
}
垃圾收集器可能已经收集了Bar


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

虽然它可能会让您收到代码审查抱怨和FxCop警告,但在没有IDisposable的情况下实现终结器本质上没有什么问题。然而,在单例上这样做并不是捕获进程或AppDomain崩溃的可靠方法


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

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

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

我支持那些认为这种设计可能不是正确的做法的人(如果随后发现确实需要两个实例,则会使其更难修复)
在您的入口点中创建对象(或延迟加载包装器对象),并通过代码将其传递到需要的位置,明确谁负责向谁提供对象,然后您可以自由地更改决定,只使用一个对象,而对代码的其余部分(使用所提供的内容)几乎没有影响

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


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

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