我什么时候可以清理存储在 C# 中的静态变量中的对象?
我有一个延迟初始化的静态变量:
public class Sqm
{
private static Lazy<Sqm> _default = new Lazy<Sqm>();
public static Sqm Default { get { return _default.Value; } }
}
注意:我刚刚变成Foo
了一个static
班级。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>
似乎没有调用它懒惰地拥有Dispose
的东西。Value
所以我不能挂钩IDisposable
模式 - 并将其用作调用的时间Shutdown
。我需要给Shutdown
自己打电话。
但当?
它是一个static
变量,它在应用程序/域/应用程序域/公寓的整个生命周期中存在一次。
是的,终结者是错误的时间
有些人明白,有些人不明白,在 a 期间尝试上传我的数据finalizer
是错误的。
///WRONG: Don't do this!
~Sqm
{
Shutdown(_values); //<-- BAD! _values might already have been finalized by the GC!
}
为什么是错的?因为values
可能已经不在了。您无法控制以什么顺序完成的对象。完全有可能values
在包含Sqm
.
怎么处置?
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
?
好吧,您应该从终结器中调用 dispose
从我的终结器调用Dispose
与从我的终结器调用一样不正确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();
然后Sqm
该类被延迟初始化。在(本机)应用程序中,对对象的引用被保存。在(本机)应用程序关闭期间,保存接口指针的变量设置为null
,并调用单例对象destructor
,它可以自行清理。
任何人都不应该打电话给任何人。不是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:人们在回答问题之前似乎要求完整、详细的示例代码。我个人认为这个问题已经包含了太多的示例代码,因为它不能帮助提出这个问题。
现在我已经添加了太多代码,这个问题已经丢失了。在问题得到证明之前,人们拒绝回答问题。现在它已经被证明是合理的,没有人会读它。
这就像诊断
就像System.Diagnostics.Trace
上课一样。人们在需要时调用它:
Trace.WriteLine("Column sort: {0} ms", sortTimeInMs);
并且永远不必再想它。
然后绝望降临
我什至非常绝望,我考虑将我的对象隐藏在一个 COMIUnknown
接口后面,它是引用计数的
public class Sqm : IUnknown
{
IUnknown _default = new Lazy<Sqm>();
}
然后希望我可以欺骗CLR 减少我界面上的引用计数。当我的引用计数变为零时,我知道一切都在关闭。
这样做的缺点是我无法让它工作。