So werden Sie benachrichtigt, bevor statische Variablen finalisiert werden

Wann kann ich Objekte bereinigen, die in statischen Variablen in C # gespeichert sind?

Ich habe also eine statische Variableträge initialisiert:

public class Sqm
{
    private static Lazy<Sqm> _default = new Lazy<Sqm>();

    public static Sqm Default { get { return _default.Value; } }
}

Hinweis: Das habe ich gerade geändertFoo ein ... zu seinstatic Klasse. Es ändert nichts an der Frage, obFoo ist statisch oder nicht. Aber einige Leute sind davon überzeugt, dass es keinen Weg gibt, einen Fall vonSqm könnte konstruiert werden, ohne zuerst eine Instanz vonFoo. Selbst wenn ichtat ein ... kreierenFoo Objekt; Selbst wenn ich 100 davon erschaffen hätte, würde es mir nicht helfen, das Problem zu lösen (von wann bis wann)"Aufräumen" ein statisches Mitglied).

Beispielnutzung

Foo.Default.TimerStart("SaveQuestion");
//...snip...
Foo.Default.TimerStop("SaveQuestion");

Jetzt meinSqm class implementiert eine Methode, die aufgerufen werden muss, wenn das Objekt nicht mehr benötigt wird und sich selbst bereinigen muss (Zustand im Dateisystem speichern, Sperren aufheben usw.). Diese MethodeMuss aufgerufen werden, bevor die Garbage Collectors ausgeführt werden (d. h. bevor der Finalizer meines Objekts aufgerufen wird):

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;
      }
   }
}

Wann und wo kann ich meine anrufen?Shutdown() Methode?

Hinweis: Ich will nicht den Entwickler, derVerwendet dasSqm Klasse, um sich Sorgen über das Anrufen zu machenShutdown. Das ist nicht sein Job. In anderen Sprachumgebungen würde er nicht müssen.

DasLazy<T> Klasse scheint nicht zu nennenDispose auf derValue es besitzt faul. Also kann ich das nicht hakenIDisposable Muster - und verwenden Sie das als die Zeit zum AnrufenShutdown. Ich muss anrufenShutdown mich selber.

Aber wenn?

Es ist einstatic variabel, es existiert einmal für die Lebensdauer der Anwendung / Domain / Appdomain / Wohnung.

Ja, der Finalizer ist die falsche Zeit

Manche Leute verstehen, und manche nicht, dass sie versuchen, meine Daten während einesfinalizer istfalsch.

///WRONG: Don't do this!
~Sqm
{
   Shutdown(_values); //<-- BAD! _values might already have been finalized by the GC!
}   

Warum ist es falsch? weilvalues könnte nicht mehr da sein. Sie steuern nicht, welche Objekte in welcher Reihenfolge finalisiert werden. Es ist durchaus möglich, dassvalues wurde vor dem Enthalten abgeschlossenSqm.

Was ist mit entsorgen?

DasIDisposable Schnittstelle und dieDispose() Methode ist einKonvention. Es gibt nichts, was dies vorschreibt, wenn mein Objekt a implementiertDispose() Methode, die es jemals aufgerufen wird. In der Tat, ichkönnte Gehen Sie voran und implementieren Sie es:

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;
      }
   }
}

Für die Person, die die Frage tatsächlich liest, könnte es sein, dass ich nichts geändert habe. Ich habe nur den Namen einer Methode von geändertAusschalten zuEntsorgen. Das Dispose-Muster ist einfach eine Konvention. Ich habe immer noch das Problem: Wann kann ich anrufen?Dispose?

Nun sollten Sie anrufen, um von Ihrem Finalizer zu entsorgen

BerufungDispose von meinem finalizer ist genauso falsch wie aufrufShutdown von meinem Finalizer (sie sind identisch falsch):

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();
   }
}

Weil wiedervalues könnte nicht mehr da sein. Der Vollständigkeit halber können wir zum vollständigen, korrekten Originalcode zurückkehren:

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
   }
}

Ich habe den Kreis geschlossen. Ich habe ein Objekt, das ich brauche"Aufräumen" bevor die Anwendungsdomäne heruntergefahren wird. Etwas in meinem Objekt muss benachrichtigt werden, wenn es aufgerufen werden kannCleanup.

Lassen Sie den Entwickler es aufrufen

Nein.

Ich migriere vorhandene Konzepte aus einer anderen Sprache in C #. Wenn ein Entwickler zufällig die globale Singleton-Instanz verwendet:

Foo.Sqm.TimerStart();

dann ist dieSqm Klasse ist faul initialisiert. In einer (systemeigenen) Anwendung wird der Verweis auf das Objekt gespeichert. Während des (systemeigenen) Herunterfahrens der Anwendung wird die Variable, die den Schnittstellenzeiger enthält, auf gesetztnullund das Singleton-Objektdestructor heißt, und es kann sich aufräumen.

Niemand sollte jemals etwas anrufen müssen. NichtCleanupnichtShutdownnichtDispose. Das Herunterfahren sollte automatisch durch die Infrastruktur erfolgen.

Was ist das C # -Äquivalent vonIch sehe mich gehen, mich aufräumen?

Erschwerend kommt hinzu, dass es zu spät ist, wenn der Müllmann das Objekt einsammelt. Die internen Statusobjekte, die ich beibehalten möchte, sind wahrscheinlich bereits finalisiert.

Es wäre einfach, wenn von ASP.net

Wenn ich garantieren könnte, dass meine Klasse von ASP.net verwendet wird, könnte ich das fragenHostingEnvironment So benachrichtigen Sie mich vor dem Herunterfahren der Domain, indem Sie mein Objekt bei ihr registrieren:

System.Web.Hosting.HostingEnvironment.RegisterObject(this);

Und implementieren Sie dieStop Methode:

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
   }
}

Nur dass meine Klasse nicht weiß, von wo ich angerufen werdeASP.netoder vonWinFormsoder vonWPFoder eine Konsolenanwendung oder Shell-Erweiterung.

Bearbeiten: Die Leute scheinen verwirrt zu sein vonwas zumIDisposable Muster existiert für. Verweise auf entferntDispose um die Verwirrung zu beseitigen.

Bearbeiten 2: Die Leute scheinen einen vollständigen, detaillierten Beispielcode zu verlangen, bevor sie die Frage beantworten. Persönlich denke ich, dass die Frage bereits zu viel Beispielcode enthält, da es nicht dazu dient, die Frage zu stellen.

Und jetzt, wo ich hinzugefügt habesooo viel Code, die Frage ist verloren gegangen. Menschen weigern sich, eine Frage zu beantworten, bis die Frage gerechtfertigt ist. Jetzt, wo es gerechtfertigt ist, wird es niemand mehr lesen.

Es ist wie eine Diagnose

Es ist wie dasSystem.Diagnostics.Trace Klasse. Die Leute nennen es, wenn sie wollen:

Trace.WriteLine("Column sort: {0} ms", sortTimeInMs);

und muss nie wieder daran denken.

Und dann setzt Verzweiflung ein

Ich war sogar so verzweifelt, dass ich überlegte, mein Objekt hinter einem COM zu versteckenIUnknown Schnittstelle, die Referenz gezählt wird

public class Sqm : IUnknown
{
   IUnknown _default = new Lazy<Sqm>();
}

Und dann könnte ich hoffentlichTrick die CLR in die Referenzzählung auf meiner Schnittstelle zu dekrementieren. Wenn mein Referenzzähler Null wird, weiß ich, dass alles heruntergefahren wird.

Der Nachteil ist, dass ich es nicht zum Laufen bringen kann.

Antworten auf die Frage(8)

Ihre Antwort auf die Frage