Jak otrzymywać powiadomienia, zanim zmienne statyczne zostaną sfinalizowane

Kiedy można czyścić obiekty przechowywane w zmiennych statycznych w C #?

Mam zmienną statycznąleniwie zainicjowany:

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

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

Uwaga: Właśnie się zmieniłemFoo byćstatic klasa. Nie zmienia to w żaden sposób, jeśliFoo jest statyczny lub nie. Ale niektórzy ludzie są przekonani, że nie ma możliwości, aby wystąpienieSqm może być skonstruowany bez uprzedniego skonstruowania instancjiFoo. Nawet gdybymzrobił StwórzFoo obiekt; nawet jeśli stworzyłbym ich 100, to nie pomogłoby mi rozwiązać problemu (kiedy do"sprzątać" członek statyczny).

Wykorzystanie próbki

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

Teraz mojeSqm klasa implementuje metodę, która musi zostać wywołana, gdy obiekt nie jest już potrzebny, i musi się oczyścić (zapisać stan do systemu archiwizacji, zwolnić blokady itp.). Ta metodamusi być wywołane przed uruchomieniem zbieraczy śmieci (tj. przed wywołaniem finalizatora mojego obiektu):

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

Kiedy i gdzie mogę zadzwonić do mnieShutdown() metoda?

Uwaga: Nie chcę programisty, któryużywa Sqm klasa, żeby się martwić o telefonShutdown. To nie jest jego praca. W innych środowiskach językowych nie musiałby.

TheLazy<T> klasa nie wydaje się dzwonićDispose naValue jest leniwie właścicielem. Więc nie mogę się doczepićIDisposable wzorzec - i używaj go jako czasu na połączenieShutdown. Muszę zadzwonićShutdown siebie.

Ale kiedy?

To jeststatic zmienna, istnieje raz dla życia aplikacji / domeny / domeny aplikacji / mieszkania.

Tak, finalizator to niewłaściwy czas

Niektórzy rozumieją, a niektórzy nie, próbując przesłać moje dane podczasfinalizer jestźle.

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

Dlaczego jest źle? Bovalues może już tam nie być. Nie kontrolujesz, które obiekty są finalizowane w jakiej kolejności. Jest to całkowicie możliwevalues został sfinalizowany przed zawartościąSqm.

Co z pozbywaniem się?

TheIDisposable interfejs iDispose() metoda toKonwencja. Nic nie dyktuje, że jeśli mój obiekt implementuje aDispose() metoda, którą kiedykolwiek zostanie nazwana. W rzeczywistościmógłby idź dalej i wdrażaj go:

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

Osoba, która faktycznie czyta to pytanie, może zauważyć, że właściwie niczego nie zmieniłem. Jedyne, co zrobiłem, to zmienić nazwę metody zZamknąć doDysponować. Wzór Dispose jest po prostu konwencją. Nadal mam problem: kiedy mogę zadzwonićDispose?

Powinieneś zadzwonić do likwidatora ze swojego finalizatora

PowołanieDispose z mojego finalizatora jest tak samo niepoprawne jak dzwonienieShutdown z mojego finalizatora (są identycznie błędne):

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

Bo znowuvalues może już tam nie być. Aby uzyskać kompletność, możemy powrócić do pełnego oryginalnego poprawnego kodu:

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

zatoczyłem koło. mam obiekt, który muszę"sprzątać" przed zamknięciem domeny aplikacji. Coś w moim obiekcie musi zostać powiadomione, gdy może zadzwonićCleanup.

Spraw, by deweloper to nazwał

Nie.

Przeprowadzam migrację istniejących pojęć z innego języka do C #. Jeśli deweloper korzysta z globalnej instancji singleton:

Foo.Sqm.TimerStart();

a późniejSqm klasa jest leniwa zainicjowana. W aplikacji (natywnej) utrzymywane jest odwołanie do obiektu. Podczas zamykania aplikacji (natywnej) zmienna przechowująca wskaźnik interfejsu jest ustawiona nanulli obiekt singletondestructor jest wywoływany i może się oczyścić.

Nikt nie powinien nigdy nic dzwonić. NieCleanup, nieShutdown, nieDispose. Wyłączenie powinno nastąpić automatycznie z infrastruktury.

Co to jest odpowiednik C #Widzę, jak odchodzę, sprzątam się?

To skomplikowane przez fakt, że jeśli pozwolisz zbieraczowi śmieci zebrać obiekt: jest za późno. Obiekty stanu wewnętrznego, które chcę utrzymać, są już prawdopodobnie sfinalizowane.

Byłoby łatwo, gdyby z ASP.net

Gdybym mógł zagwarantować, że moja klasa była używana z ASP.net, mógłbym zapytaćHostingEnvironment powiadomić, zanim domena zostanie zamknięta, rejestrując przy tym swój obiekt:

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

I zaimplementujStop metoda:

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

Z wyjątkiem tego, że moja klasa nie wie, czy będę do mnie dzwonićASP.netlub zWinFormslub zWPFlub aplikacja konsoli lub rozszerzenie powłoki.

Edytować: Ludzie wydają się być zdezorientowanicoIDisposable istnieje wzór. Usunięto odniesienia doDispose w celu usunięcia zamieszania.

Edytuj 2: Ludzie wydają się wymagać pełnego, szczegółowego, przykładowego kodu, zanim odpowiedzą na pytanie. Osobiście uważam, że pytanie zawiera już zbyt wiele przykładowego kodu, ponieważ nie służy do zadawania pytań.

A teraz dodałemsooo dużo kodu, pytanie zostało utracone. Ludzie odmawiają odpowiedzi na pytanie, dopóki pytanie nie zostanie uzasadnione. Teraz, kiedy jest to uzasadnione, nikt tego nie przeczyta.

To jak diagnostyka

To jest jakSystem.Diagnostics.Trace klasa. Ludzie nazywają to, kiedy chcą:

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

i nigdy więcej nie muszę o tym myśleć.

A potem zaczyna się desperacja

Byłem nawet wystarczająco zdesperowany, że rozważałem ukrycie mojego obiektu za COMIUnknown interfejs, który jest liczony jako odniesienie

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

I mam nadzieję, że taksztuczka CLR w celu zmniejszenia liczby odwołań w moim interfejsie. Kiedy mój licznik odniesie zero, wiem, że wszystko się wyłącza.

Wadą tego jest to, że nie mogę tego sprawić.

questionAnswers(8)

yourAnswerToTheQuestion