我有一个静态变量是懒加载的:
public class Sqm
{
private static Lazy<Sqm> _default = new Lazy<Sqm>();
public static Sqm Default { get { return _default.Value; } }
}
注意:我刚把
Foo
改成了一个静态类。如果 Foo
是静态的或者不是静态的,这并不会改变问题。但是有些人认为没有办法在构造 Sqm
实例之前构造 Foo
实例。即使我创建一个 Foo
对象; 即使我创建 100 个对象,这也无法帮助我解决(何时“清理”静态成员)的问题。用法示例
Foo.Default.TimerStart("SaveQuestion");
//...snip...
Foo.Default.TimerStop("SaveQuestion");
现在,我的
Sqm
类实现了一个方法,必须在对象不再需要时调用,并且需要清理自身(将状态保存到文件系统、释放锁等)。这个方法必须在垃圾收集器运行之前调用(即在调用我的对象的终结器之前)。public class Sqm
{
var values = new List<String>();
Boolean shutdown = false;
protected void Cleanup(ICollection stuff)
{
WebRequest http = new HttpWebRequest();
http.Open("POST", "https://stackoverflow.com/SubmitUsageTelemetry");
http.PostBody = stuff;
http.Send();
}
public void Shutdown()
{
if (!alreadyShutdown)
{
Cleanup(values);
alreadyShutdown = true;
}
}
}
什么时候,以及在哪里,可以调用我的Shutdown()
方法?
注意: 我不希望使用Sqm
类的开发人员担心调用Shutdown
。那不是他的工作。在其他语言环境中,他不必这样做。
Lazy<T>
类似乎没有在它懒惰地拥有的Value
上调用Dispose
。因此,我无法挂钩IDisposable
模式 - 并将其用作调用Shutdown
的时间。我需要自己调用Shutdown
。
但是在什么时候?
它是一个静态
变量,它存在于应用程序/域/应用程序域/公寓的生命周期内。
是的,终结器的时间不对
有些人理解,有些人不理解,在finalizer
期间尝试上传我的数据是错误的。
///WRONG: Don't do this!
~Sqm
{
Shutdown(_values); //<-- BAD! _values might already have been finalized by the GC!
}
为什么这是错误的?因为
values
可能已经不存在了。您无法控制对象以何种顺序进行终结。完全有可能在包含Sqm
之前,values
就已经被终结了。
< h1 >那么Dispose呢?
< p > IDisposable
接口和Dispose()
方法是一种约定。没有任何规定,如果我的对象实现了Dispose()
方法,它就一定会被调用。事实上,我可以继续实现它:
public class Sqm : IDisposable
{
var values = new List<String>();
Boolean alreadyDiposed = false;
protected void Cleanup(ICollection stuff)
{
WebRequest http = new HttpWebRequest();
http.Open("POST", "https://stackoverflow.com/SubmitUsageTelemetry");
http.PostBody = stuff;
http.Send();
}
public void Dispose()
{
if (!alreadyDiposed)
{
Cleanup(values);
alreadyDiposed = true;
}
}
}
针对实际阅读此问题的人,您可能会注意到我实际上没有改变任何内容。我唯一做的是将方法的名称从Shutdown更改为Dispose。Dispose模式只是一种约定。我的问题仍然存在:何时可以调用Dispose
?
那你应该从finalizer中调用Dispose
从我的finalizer调用Dispose
与从我的finalizer调用Shutdown
一样不正确(它们完全错误):
public class Sqm : IDisposable
{
var values = new List<String>();
Boolean alreadyDiposed = false;
protected void Cleanup(ICollection stuff)
{
WebRequest http = new HttpWebRequest();
http.Open("POST", "https://stackoverflow.com/SubmitUsageTelemetry");
http.PostBody = stuff;
http.Send();
}
public void Dispose()
{
if (!alreadyDiposed)
{
Cleanup(_values); // <--BUG: _values might already have been finalized by the GC!
alreadyDiposed = true;
}
}
~Sqm
{
Dispose();
}
}
因为,values
可能不再存在。为了完整起见,我们可以返回到完整的原始正确代码:
public class Sqm : IDisposable
{
var values = new List<String>();
Boolean alreadyDiposed = false;
protected void Cleanup(ICollection stuff)
{
WebRequest http = new HttpWebRequest();
http.Open("POST", "https://stackoverflow.com/SubmitUsageTelemetry");
http.PostBody = stuff;
http.Send();
}
protected void Dispose(Boolean itIsSafeToAlsoAccessManagedResources)
{
if (!alreadyDiposed)
{
if (itIsSafeToAlsoAccessManagedResources)
Cleanup(values);
alreadyDiposed = true;
}
}
public void Dispose()
{
this.Dispose(true);
GC.SuppressFinalize(this);
}
~Sqm
{
Dispose(false); //false ==> it is not safe to access values
}
}
我已经完整地经历了一次循环。我有一个需要在应用程序域关闭之前“清理”的对象。我的对象中的某些内容需要在可以调用 Cleanup
时得到通知。
让开发者调用它
不行。
我正在将从另一种语言中现有的概念迁移到C#。如果开发者恰好使用全局单例实例:
Foo.Sqm.TimerStart();
如果是(native)应用程序,那么
Sqm
类会进行惰性初始化。在(native)应用程序中,会持有对该对象的引用。在(native)应用程序关闭期间,保存接口指针的变量会被设置为null
,并且单例对象的析构函数
会被调用,它可以自行清理。
没有人应该去调用任何东西。不是Cleanup
,也不是Shutdown
,更不是Dispose
。停机应该由基础设施自动完成。
“我看到自己要离开了,清理一下自己”,在C#中有什么等效的语句?
如果您让垃圾收集器收集该对象,情况就会变得复杂:太晚了。我想持久化的内部状态对象可能已经被终结了。
如果是从ASP.net开始的话,这将会很容易
如果我能保证我的类来自ASP.net,我可以通过向HostingEnvironment
注册我的对象来要求在域关闭之前通知:
System.Web.Hosting.HostingEnvironment.RegisterObject(this);
并实现 Stop
方法:
public class Sqm : IDisposable, IRegisteredObject
{
var values = new List<String>();
Boolean alreadyDiposed = false;
protected void Cleanup(ICollection stuff)
{
WebRequest http = new HttpWebRequest();
http.Open("POST", "https://stackoverflow.com/SubmitUsageTelemetry");
http.PostBody = stuff;
http.Send();
}
protected void Dispose(Boolean itIsSafeToAlsoAccessManagedResources)
{
if (!alreadyDiposed)
{
if (itIsSafeToAlsoAccessManagedResources)
Cleanup(values);
alreadyDiposed = true;
}
}
public void Dispose()
{
this.Dispose(true);
GC.SuppressFinalize(this);
}
Sqm
{
//Register ourself with the ASP.net hosting environment,
//so we can be notified with the application is shutting down
HostingEnvironment.RegisterObject(this); //asp.net will call Stop() when it's time to cleanup
}
~Sqm
{
Dispose(false); //false ==> it is not safe to access values
}
// IRegisteredObject
protected void Stop(Boolean immediate)
{
if (immediate)
{
//i took too long to shut down; the rug is being pulled out from under me.
//i had my chance. Oh well.
return;
}
Cleanup(); //or Dispose(), both good
}
}
除了我的班级不知道我会从ASP.net、WinForms、WPF、控制台应用程序或Shell扩展中被调用。 编辑: 人们似乎对为什么存在
IDisposable
模式感到困惑。为了消除困惑,删除了对Dispose
的引用。
编辑2: 人们似乎要求在回答问题之前提供完整详细的示例代码。个人认为问题已经包含了太多的示例代码,因为它并没有帮助提出问题。现在我添加了sooo多的代码,问题已经丢失了。人们拒绝回答问题,直到问题得到证明。现在问题已经得到证明,没有人会阅读它。
就像诊断一样
这就像System.Diagnostics.Trace
类。人们在需要时调用它:Trace.WriteLine("Column sort: {0} ms", sortTimeInMs);
我希望你永远不必再去考虑它。
然后绝望开始了
我甚至绝望到考虑将我的对象隐藏在一个COM IUnknown
接口后面,这个接口是引用计数的。
public class Sqm : IUnknown
{
IUnknown _default = new Lazy<Sqm>();
}
然后希望我可以欺骗CLR来减少我的接口引用计数。当我的引用计数变为零时,我知道一切都在关闭。
缺点是我无法使其工作。
Foo
可能永远不会被创建,因此我将无法知道它何时被销毁。 - Ian BoydFoo
,那么它只会在卸载 AppDomain 时被销毁。 - Sriram SakthivelSqm
也永远不会被创建,对吗?你的问题是什么?如果你在谈论默认的AppDomain
,那么没有办法得到通知。如果你自己创建了AppDomain
,那么可以订阅DomainUnload事件。 - Sriram Sakthivel