Cómo ser notificado antes de que se finalicen las variables estáticas

¿Cuándo puedo limpiar objetos almacenados en variables estáticas en C #?

Tengo una variable estática que esinicializado perezosamente:

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

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

Nota: Que acabo de cambiarFoo ser unstatic clase. No cambia la pregunta de ninguna manera siFoo Es estático o no. Pero algunas personas están convencidas de que no hay forma de que una instancia deSqm podría construirse sin construir primero una instancia deFoo. Incluso si yohizo crear unFoo objeto; incluso si creé 100 de ellos, no me ayudaría a resolver el problema (de cuándo"limpiar" un miembro estático).

Uso de la muestra

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

Ahora miSqm La clase implementa un método que debe llamarse cuando el objeto ya no es necesario, y necesita limpiarse (guardar el estado en el sistema de archivo, liberar bloqueos, etc.). Este métododebe ser llamado antes de que se ejecute el recolector de basura (es decir, antes de que se llame al finalizador de mi objeto):

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

¿Cuándo y dónde puedo llamar a miShutdown() ¿método?

Nota: No quiero que el desarrollador queusos laSqm clase para tener que preocuparse por llamarShutdown. Ese no es su trabajo. En otros entornos lingüísticos no tendría que hacerlo.

losLazy<T> la clase no parece llamarDispose sobre elValue lo posee perezosamente Así que no puedo enganchar elIDisposable patrón - y usar eso como el tiempo para llamarShutdown. Necesito llamarShutdown mí mismo.

¿Pero cuando?

Es unstatic variable, existe una vez durante la vida de la aplicación / dominio / dominio de aplicación / apartamento.

Sí, el finalizador es el momento equivocado.

Algunas personas entienden, y otras no, que intentar cargar mis datos durante unafinalizer esincorrecto.

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

¿Por qué está mal? Porquevalues Puede que ya no esté allí. No controlas qué objetos se finalizan en qué orden. Es totalmente posible quevalues se finalizó antes de la contenciónSqm.

¿Qué hay de disponer?

losIDisposable interfaz, y laDispose() método es unconvención. No hay nada que dicte que si mi objeto implementa unDispose() Método que jamás se llamará. En efecto, yopodría adelante e implementalo:

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

Para la persona que realmente lee la pregunta, puede notar que en realidad no cambié nada. Lo único que hice fue cambiar el nombre de un método deApagar aDisponer. El patrón de Disposición es simplemente una convención. Todavía tengo el problema: ¿cuándo puedo llamar?Dispose?

Pues deberías llamar a disponer de tu finalizador.

VocaciónDispose de mi finalizador es tan incorrecto como llamarShutdown de mi finalizador (están igualmente equivocados):

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

Porque, de nuevo,values Puede que ya no esté allí. Para completar, podemos volver al código completo original correcto:

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

He completado el círculo. tengo un objeto que necesito"limpiar" antes de que el dominio de la aplicación se apague. Algo dentro de mi objeto debe ser notificado cuando puede llamarCleanup.

Haz que el desarrollador lo llame

No.

Estoy migrando conceptos existentes de otro lenguaje a C #. Si un desarrollador utiliza la instancia global de singleton:

Foo.Sqm.TimerStart();

entonces elSqm la clase es perezosa inicializada En una aplicación (nativa), se mantiene la referencia al objeto. Durante el cierre de la aplicación (nativa), la variable que mantiene el puntero de la interfaz se establece ennull, y el objeto singletondestructor Se llama, y ​​puede limpiarse.

Nadie debería tener que llamar a nada. NoCleanupnoShutdownnoDispose. El cierre debe ocurrir automáticamente por la infraestructura.

¿Cuál es el equivalente de C # deMe veo yendo, me limpio.?

Se complica por el hecho de que si deja que el recolector de basura recoja el objeto: es demasiado tarde. Es probable que los objetos de estado interno que quiero persistir ya estén finalizados.

Sería fácil si desde ASP.net

Si pudiera garantizar que mi clase estaba siendo utilizada desde ASP.net, podría preguntar alHostingEnvironment para notificar antes de que el dominio se apague registrando mi objeto con él:

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

Y poner en práctica elStop método:

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

Excepto que mi clase no sabe si me llamaránASP.net, o deWinForms, o deWPF, o una aplicación de consola, o extensión de shell.

Editar: La gente parece estar confundida porqueIDisposable existe un patrón para. Se eliminaron las referencias aDispose con el fin de eliminar la confusión.

Editar 2: La gente parece estar exigiendo un código de ejemplo completo y detallado antes de responder la pregunta. Personalmente, creo que la pregunta ya contiene demasiado código de ejemplo, ya que no sirve para ayudar a hacer la pregunta.

Y ahora que he añadido.muuuy Mucho código, la pregunta se ha perdido. La gente se niega a responder una pregunta hasta que la pregunta haya sido justificada. Ahora que ha sido justificado, nadie lo leerá.

Es como diagnostico

Es como elSystem.Diagnostics.Trace clase. La gente lo llama cuando quiere:

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

Y nunca tendré que volver a pensarlo.

Y entonces la desesperación se establece en

Estaba lo suficientemente desesperado, que consideré ocultar mi objeto detrás de un COMIUnknown interfaz, que es referencia contada

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

Y entonces ojalá pudieratruco el CLR en decrementar el recuento de referencia en mi interfaz. Cuando mi recuento de referencias se vuelve cero, sé que todo se está cerrando.

La desventaja de eso es que no puedo hacer que funcione.

Respuestas a la pregunta(8)

Su respuesta a la pregunta