C# 带有终结器但不可IDisposable的Singleton
以下是我从“CLR via C#”和“Effective C#”以及其他资源中对IDisposable和finalizer的理解:C# 带有终结器但不可IDisposable的Singleton,c#,.net,singleton,idisposable,C#,.net,Singleton,Idisposable,以下是我从“CLR via C#”和“Effective C#”以及其他资源中对IDisposable和finalizer的理解: IDisposable用于决定性地清理托管和非托管资源 负责非托管资源(例如文件句柄)的类应实现IDisposable,并提供终结器,以确保即使客户端代码未在实例上调用Dispose(),它们也会被清除 仅负责托管资源的类不应实现终结器 如果您有一个终结器,那么您必须实现IDisposable(这允许客户端代码做正确的事情并调用Dispose(),而终结器可以防止
- IDisposable用于决定性地清理托管和非托管资源
- 负责非托管资源(例如文件句柄)的类应实现IDisposable,并提供终结器,以确保即使客户端代码未在实例上调用Dispose(),它们也会被清除李>
- 仅负责托管资源的类不应实现终结器
- 如果您有一个终结器,那么您必须实现IDisposable(这允许客户端代码做正确的事情并调用Dispose(),而终结器可以防止资源在忘记时泄漏)
这是否合理的做法?如果不是,为什么不呢?还有什么更好的选择?我首先要提到的是,面向对象的设计模式及其后果并不总是影响每一种语言的决策,即使是在面向对象的语言中。您当然可以找到更容易在一种语言(Smalltalk)和另一种语言(C++)中实现的经典设计模式 也就是说,我不确定我是否同意这样一个前提,即单例实例应该只在应用程序结束时处理。我在阅读的设计模式描述中没有提到(或)将此作为此模式的属性。单例应该确保在任何时刻只存在类的一个实例;这并不意味着只要应用程序存在,它就必须存在 我有一种感觉,实际上,在应用程序的大部分生命周期中,确实存在许多单例。但是,考虑使用TCP连接与服务器通信的应用程序,但也可以存在于断开连接模式中。当连接时,您需要一个单例来维护连接信息和连接状态。一旦断开连接,您可能希望保留相同的单例-或者您可以处置单例。虽然有些人可能认为保留单例(我甚至可能是其中之一)更有意义,但设计模式本身没有任何东西阻止您处理它——如果重新创建连接,单例可以再次实例化,因为此时不存在它的实例
换句话说,您可以创建这样的场景:单例具有IDisposable是合乎逻辑的。只要您的终结器不调用任何其他托管对象上的方法(如Dispose),您就可以了。请记住,定稿顺序不是确定的。也就是说,如果您的单例对象Foo持有对需要处理的对象栏的引用,则无法可靠地写入:
~Foo()
{
Bar.Dispose();
}
垃圾收集器可能已经收集了Bar
冒着陷入一堆OO-goo(即引发战争)的风险,使用单例的一种替代方法是使用静态类。虽然它可能会让您收到代码审查抱怨和FxCop警告,但在没有IDisposable的情况下实现终结器本质上没有什么问题。然而,在单例上这样做并不是捕获进程或AppDomain崩溃的可靠方法
冒着提供主观设计建议的风险:如果对象确实是无状态的,则将其设置为静态类。若它是有状态的,那个么问一下为什么它是单例变量:您正在创建一个可变的全局变量。如果您试图捕获应用程序关闭,请在主循环退出时处理它。如果非托管资源仅在应用程序退出时释放,您甚至不需要担心终结器,因为进程卸载应该为您处理此问题 如果你有多个应用程序域,你想处理一个应用程序域卸载,这是一个可能的问题,但可能是一个你不需要关心的问题 我支持那些认为这种设计可能不是正确的做法的人(如果随后发现确实需要两个实例,则会使其更难修复)
在您的入口点中创建对象(或延迟加载包装器对象),并通过代码将其传递到需要的位置,明确谁负责向谁提供对象,然后您可以自由地更改决定,只使用一个对象,而对代码的其余部分(使用所提供的内容)几乎没有影响除了对任何特定情况的适用性
我认为处理单身汉没有什么错。与惰性实例化相结合,它只是意味着如果暂时不需要资源,则释放资源,然后根据需要重新获取资源。如果要创建带有终结器的单例,可能应该将对它的静态引用设为WeakReference。这将需要一些额外的工作来确保访问器中的线程安全,但这将允许在没有人使用单例时对其进行垃圾收集(如果有人随后调用GetInstance()方法,他们将获得一个新实例)。如果使用静态强引用,即使没有其他引用,它也会保持singleton实例的活动状态