When can i cleanup objects stored in static variables in C#?
I have a static variable that is lazily initialized:
public class Sqm
{
private static Lazy<Sqm> _default = new Lazy<Sqm>();
public static Sqm Default { get { return _default.Value; } }
}
Note: That i've just changed Foo
to be a static
class. It doesn't change the question in any way if Foo
is static or not. But some people are convinced that there is no way that an instance of Sqm
could be constructed without first constructing an instance of Foo
. Even if i did create a Foo
object; even if i created 100 of them, it wouldn't help me solve the problem (of when to "cleanup" a static member).
Sample usage
Foo.Default.TimerStart("SaveQuestion");
//...snip...
Foo.Default.TimerStop("SaveQuestion");
Now, my Sqm
class implements a method that must be called when the object is no longer needed, and needs to clean itself up (save state to filing system, release locks, etc). This method must be called before the garbage collectors runs (i.e. before my object's finalizer is called):
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;
}
}
}
When, and where, can i call my Shutdown()
method?
Note: I don't want the developer who uses the Sqm
class to have to worry about calling Shutdown
. That's not his job. In other language environments he would not have to.
The Lazy<T>
class does not seem to call Dispose
on the Value
it lazily owns. So i cannot hook the IDisposable
pattern - and use that as the time to call Shutdown
. I need to call Shutdown
myself.
But when?
It's a static
variable, it exists once for the life of the application/domain/appdomain/apartment.
Yes, the finalizer is the wrong time
Some people do understand, and some people don't, that trying to upload my data during a finalizer
is wrong.
///WRONG: Don't do this!
~Sqm
{
Shutdown(_values); //<-- BAD! _values might already have been finalized by the GC!
}
Why is it wrong? Because values
might not be there anymore. You don't control what objects are finalized in what order. It is entirely possible that values
was finalized before the containing Sqm
.
What about dispose?
The IDisposable
interface, and the Dispose()
method is a convention. There is nothing that dictates that if my object implements a Dispose()
method that it will ever be called. In fact, i could go ahead and implement it:
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;
}
}
}
To the person actually reading the question, you might notice that i didn't actually change anything. The only thing i did was to change the name of a method from Shutdown to Dispose. The Dispose pattern is simply a convention. i still have the problem: when can i call Dispose
?
Well you should call dispose from your finalizer
Calling Dispose
from my finalizer is just as incorrect as calling Shutdown
from my finalizer (they are identically wrong):
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();
}
}
Because, again, values
might not be there anymore. For completeness, we can return to the full original correct code:
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
}
}
i've come full circle. i have an object which i need to "cleanup" before the application domain shuts down. Something inside my object needs to be notified when it can call Cleanup
.
Make the developer call it
No.
i'm migrating existing concepts from another language into C#. If a developer happens to use the global singleton instance:
Foo.Sqm.TimerStart();
then the Sqm
class is lazy initialized. In a (native) application, the reference to the object is held. During (native) application shutdown, the variable holding the interface pointer is set to null
, and the singleton object's destructor
is called, and it can clean itself up.
Nobody should ever have to call anything. Not Cleanup
, not Shutdown
, not Dispose
. Shutdown should happen automatically by the infrastructure.
What is the C# equivalent of i see myself going away, clean myself up?
It's complicated by the fact that if you let the garbage collector collect the object: it's too late. The internal state objects i want to persist are likely already finalized.
It would be easy if from ASP.net
If i could guarantee that my class were being used from ASP.net, i could ask the HostingEnvironment
to notify before the domain shuts down by registering my object with it:
System.Web.Hosting.HostingEnvironment.RegisterObject(this);
And implement the the Stop
method:
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
}
}
Except my class doesn't know if i'll be called from ASP.net, or from WinForms, or from WPF, or a console application, or shell extension.
Edit: People seem to be confused by what the IDisposable
pattern exists for. Removed references to Dispose
in order to remove the confusion.
Edit 2: People seem to be demanding full, detailed, example code before they will answer the question. Personally i think the question already contains too much example code, as it doesn't serve to help ask the question.
And now that i've added sooo much code, the question has been lost. People refuse to answer a question until the question has been justified. Now that it's been justified, nobody will read it.
It's like diagnostics
It's like the System.Diagnostics.Trace
class. People call it when they want:
Trace.WriteLine("Column sort: {0} ms", sortTimeInMs);
and never have to think of it again.
And then desperation sets in
i was even desperate enough, that i considered hiding my object behind an COM IUnknown
interface, which is reference counted
public class Sqm : IUnknown
{
IUnknown _default = new Lazy<Sqm>();
}
And then hopefully i could trick the CLR into decrementing the reference count on my interface. When my reference count becomes zero, i know everything is shutting down.
The downside of that is that i cannot make it work.
See Question&Answers more detail:
os