Właściwe wykorzystanie interfejsu IDisposable

Wiem z lektury dokumentacji Microsoftu , że "podstawowym" zastosowaniem interfejsu IDisposable jest czyszczenie niezarządzanych zasobów.

Dla mnie "niezarządzane" oznacza takie rzeczy jak połączenia z bazami danych, gniazda, klamki do okien itp. Ale widziałem kod, w którym metoda Dispose() jest zaimplementowana do wolnych zarządzanych zasobów, co wydaje mi się zbędne, ponieważ garbage collector powinien się tym za Ciebie zająć.

Na przykład:

public class MyCollection : IDisposable
{
    private List<String> _theList = new List<String>();
    private Dictionary<String, Point> _theDict = new Dictionary<String, Point>();

    // Die, clear it up! (free unmanaged resources)
    public void Dispose()
    {
        _theList.clear();
        _theDict.clear();
        _theList = null;
        _theDict = null;
    }

Mój pytanie tylko, czy to sprawia, że garbage collector jest wolny od pamięci używanej przez MyCollection szybciej niż normalnie?

Edit: do tej pory ludzie opublikowali kilka dobrych przykładów użycia IDisposable do czyszczenia niezarządzanych zasobów, takich jak połączenia z bazami danych i bitmapy. Ale załóżmy, że _theList w powyższym kodzie zawierało milion łańcuchów, a ty chciałeś uwolnić tę pamięć Teraz , zamiast czekać na garbage collector. Czy powyższy kod by tego dokonał?

Author: Elanis, 2009-02-11

19 answers

Celem pozbycia się jest uwolnienie niezarządzanych zasobów. Trzeba to zrobić w pewnym momencie, w przeciwnym razie nigdy nie zostaną oczyszczone. Garbage collector nie wie jak wywołać DeleteHandle() na zmiennej typu IntPtr, nie wie czy czy nie musi wywołać DeleteHandle().

Uwaga : Co to jest niezarządzany zasób ? Jeśli znalazłeś go w programie Microsoft. NET Framework: jest zarządzany. Jeśli poszedłeś grzebać w MSDN ty, to jest niezarządzane. Wszystko, czego użyłeś do wywołania P / Invoke, aby wydostać się z przyjemnego, wygodnego świata wszystkiego, co dostępne jest dla Ciebie w.NET Framework, nie jest zarządzane – a Ty jesteś teraz odpowiedzialny za jego sprzątanie.

Obiekt, który stworzyłeś, musi ujawnićjakąś metodę, którą może wywołać świat zewnętrzny, aby oczyścić niezarządzane zasoby. Metoda może być nazwana jak chcesz:

public void Cleanup()

Lub

public void Shutdown()

Ale zamiast tego jest znormalizowana nazwa dla tej metody:

public void Dispose()

Powstał nawet interfejs, IDisposable, który ma tylko jedną metodę:

public interface IDisposable
{
   void Dispose()
}

Więc sprawiasz, że Twój obiekt naraża interfejs IDisposable, i w ten sposób obiecujesz, że napisałeś tę pojedynczą metodę, aby oczyścić swoje niezarządzane zasoby: {]}

public void Dispose()
{
   Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle);
}
I gotowe. Tylko, że stać Cię na więcej.

Co jeśli twój obiekt przydzielił 250MB System.Rysunek.Bitmap (tj. . NET managed Bitmap class) jako jakiś bufor ramki? Oczywiście, jest to zarządzany obiekt. NET, a garbage collector go uwolni. Ale czy naprawdę chcesz zostawić 250MB pamięci po prostu siedząc tam-czekając na to, aż Śmieciarz w końcu przyjdzie i go uwolni? Co jeśli istnieje otwarte połączenie z bazą danych ? Z pewnością nie chcemy, aby to połączenie było otwarte i czekało na sfinalizowanie obiektu przez GC.

Jeśli użytkownik wywołał Dispose() (czyli nie dłuższy plan korzystania z obiektu) dlaczego nie pozbyć się tych marnotrawnych bitmap i połączeń z bazami danych?

Więc teraz będziemy:

    [[102]} pozbądź się niezarządzanych zasobów (bo musimy) i [102]}pozbądź się zarządzanych zasobów (ponieważ chcemy być pomocni)

Zaktualizujmy więc naszą metodę Dispose(), aby pozbyć się tych zarządzanych obiektów:

public void Dispose()
{
   //Free unmanaged resources
   Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle);

   //Free managed resources too
   if (this.databaseConnection != null)
   {
      this.databaseConnection.Dispose();
      this.databaseConnection = null;
   }
   if (this.frameBufferImage != null)
   {
      this.frameBufferImage.Dispose();
      this.frameBufferImage = null;
   }
}

I wszystko jest dobre, Z wyjątkiem tego, że możesz zrobić lepiej !


Co jeśli osoba zapomniałeś zadzwonić do Twojego obiektu? Wtedy wyciekną jakieś niezarządzane zasoby!

Uwaga: Nie wyciekną zarządzane zasoby , ponieważ w końcu garbage collector uruchomi się w wątku w tle i uwolni pamięć związaną z nieużywanymi obiektami. Dotyczy to Twojego obiektu i wszystkich zarządzanych obiektów, których używasz(np. Bitmap i DbConnection).

Jeśli osoba zapomniała zadzwonić Dispose(), możemy wciąż uratuj ich bekon! Wciąż mamy sposób na nazwanie go dla ich: kiedy Śmieciarz w końcu uwolni (tzn. sfinalizuje) nasz obiekt.

Uwaga: garbage collector ostatecznie uwolni wszystkie zarządzane obiekty. Kiedy to robi, nazywa Finalize metoda na obiekcie. GC nie wie, czy care, about your usunąć To było tylko imię, które wybraliśmy dla metodę, którą wywołujemy, gdy chcemy uzyskać pozbądź się niezarządzanych rzeczy.

Zniszczenie naszego obiektu przez śmieciarza to idealny czas na uwolnienie tych nieznośnych, niezarządzanych zasobów. Robimy to poprzez nadpisanie metody Finalize().

Uwaga: W C# nie nadpisujesz jawnie metody Finalize(). Piszesz metodę, która wygląda jak a C++ destructor, a kompilator przyjmuje, że jest to twoja implementacja Finalize() metoda:

~MyObject()
{
    //we're being finalized (i.e. destroyed), call Dispose in case the user forgot to
    Dispose(); //<--Warning: subtle bug! Keep reading!
}
Ale w tym kodzie jest błąd. Widzisz, garbage collector działa na wątku W Tle ; nie znasz kolejności, w jakiej dwa obiekty są niszczone. Jest całkowicie możliwe, że w Twoim kodzie Dispose() nie ma już obiektu zarządzanego, którego chcesz się pozbyć (ponieważ chciałeś być pomocny):
public void Dispose()
{
   //Free unmanaged resources
   Win32.DestroyHandle(this.gdiCursorBitmapStreamFileHandle);

   //Free managed resources too
   if (this.databaseConnection != null)
   {
      this.databaseConnection.Dispose(); //<-- crash, GC already destroyed it
      this.databaseConnection = null;
   }
   if (this.frameBufferImage != null)
   {
      this.frameBufferImage.Dispose(); //<-- crash, GC already destroyed it
      this.frameBufferImage = null;
   }
}

Więc potrzebujesz sposobu, aby Finalize() powiedzieć Dispose(), że powinno nie dotykać żadnych zarządzanych zasobów (ponieważ[66]}może ich już nie być {67]}), jednocześnie uwalniając niezarządzane zasoby.

Standardowy wzorzec to mieć Finalize() i Dispose() oba wywołują trzeci (!) metoda; gdzie przekazujesz boolowskie powiedzenie, Jeśli wywołujesz je z Dispose() (w przeciwieństwie do Finalize()), co oznacza, że wolne zarządzane zasoby są bezpieczne.

Ta wewnętrzna metoda może mieć jakąś arbitralną nazwę, jak "CoreDispose", lub "MyInternalDispose", ale jest tradycją Dispose(Boolean):

protected void Dispose(Boolean disposing)

Ale bardziej pomocną nazwą parametru może być:

protected void Dispose(Boolean itIsSafeToAlsoFreeManagedObjects)
{
   //Free unmanaged resources
   Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle);

   //Free managed resources too, but only if I'm being called from Dispose
   //(If I'm being called from Finalize then the objects might not exist
   //anymore
   if (itIsSafeToAlsoFreeManagedObjects)  
   {    
      if (this.databaseConnection != null)
      {
         this.databaseConnection.Dispose();
         this.databaseConnection = null;
      }
      if (this.frameBufferImage != null)
      {
         this.frameBufferImage.Dispose();
         this.frameBufferImage = null;
      }
   }
}

I zmieniasz implementację metody IDisposable.Dispose() na:

public void Dispose()
{
   Dispose(true); //I am calling you from Dispose, it's safe
}

I twój finalizator do:

~MyObject()
{
   Dispose(false); //I am *not* calling you from Dispose, it's *not* safe
}

Uwaga: jeśli twój obiekt pochodzi z obiektu, który implementuje Dispose, nie zapomnij wywołać ich metody base Dispose po nadpisaniu Dispose:

public override void Dispose()
{
    try
    {
        Dispose(true); //true: safe to free managed resources
    }
    finally
    {
        base.Dispose();
    }
}

I wszystko jest dobre, Z wyjątkiem tego, że możesz zrobić lepiej !


Jeśli użytkownik wywoła Dispose() na Twoim obiekcie, to wszystko zostało wyczyszczone. Później, gdy pojawi się garbage collector i wywołania Finalize, wywoła Dispose ponownie.

Nie tylko jest to marnotrawstwo, ale jeśli twój obiekt ma śmieciowe odniesienia do obiektów, które już usunąłeś z ostatniego wywołania {56]} do Dispose(), spróbujesz je ponownie usunąć!

Zauważysz w moim kodzie, że byłem ostrożny, aby usunąć odniesienia do obiektów, które mam usuwane, więc nie próbuję wywoływać Dispose na odnośniku obiektu śmieci. Ale to nie powstrzymało subtelnego pluskwy przed zakradaniem się.

Gdy użytkownik wywoła Dispose(): uchwyt CursorFileBitmapIconServiceHandle zostanie zniszczony. Później, gdy garbage collector uruchomi, spróbuje ponownie zniszczyć ten sam uchwyt.

protected void Dispose(Boolean iAmBeingCalledFromDisposeAndNotFinalize)
{
   //Free unmanaged resources
   Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle); //<--double destroy 
   ...
}

Sposób, w jaki to naprawisz, to powiedz garbage collectorowi, że nie musi zawracać sobie głowy finalizacją obiektu – jego zasoby zostały już wyczyszczone i nie potrzeba więcej pracy. Można to zrobić wywołując GC.SuppressFinalize() w metodzie Dispose():

public void Dispose()
{
   Dispose(true); //I am calling you from Dispose, it's safe
   GC.SuppressFinalize(this); //Hey, GC: don't bother calling finalize later
}

Teraz, gdy użytkownik wywołał Dispose(), mamy:

    Uwolnienie zasobów niezarządzanych]} [[102]}uwolnione zarządzane zasoby [103]}

Nie ma sensu w GC uruchamianie finalizera-wszystko załatwione.

Nie mogę użyć Finalize do czyszczenia niezarządzanych zasobów?

Dokumentacja dla Object.Finalize mówi:

The Finalize metoda jest używana do wykonywania operacji czyszczenia niezarządzanych zasobów przechowywanych przez bieżący obiekt przed zniszczeniem obiektu.

Ale dokumentacja MSDN mówi również, że dla IDisposable.Dispose:

Wykonuje zdefiniowane przez aplikację zadania związane z zwalnianiem, zwalnianiem lub resetowaniem niezarządzanych zasobów.

Więc o co chodzi? Który z nich jest dla mnie miejscem do sprzątania niezarządzanych zasobów? Odpowiedź brzmi:

To twój wybór! Ale wybierz Dispose.
Z pewnością możesz umieścić swoje niezarządzane oczyszczanie w finalizatorze:]}
~MyObject()
{
   //Free unmanaged resources
   Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle);

   //A C# destructor automatically calls the destructor of its base class.
}

Problem w tym, że nie masz pojęcia, kiedy garbage collector zajmie się finalizacją Twojego obiektu. Twoje Nie zarządzane, nie potrzebne, nie używane zasoby natywne pozostaną w pobliżu, dopóki nie uruchomi się garbage collector w końcu . Następnie wywoła metodę finalizera; czyszczenie niezarządzanych zasobów. Dokumentacja obiektu .Finalize wskazuje na to out:

Dokładny czas wykonania finalizera jest nieokreślony. Aby zapewnić deterministyczne uwolnienie zasobów dla instancji twojej klasy, zaimplementuj metodę Close lub dostarcz IDisposable.Dispose wdrożenie.

Jest to zaleta używania Dispose do czyszczenia niezarządzanych zasobów; poznajesz i kontrolujesz, kiedy niezarządzane zasoby są sprzątane. Ich zniszczenie jest "deterministyczne" .

Aby odpowiedzieć na oryginał pytanie: Dlaczego nie zwolnić pamięci teraz, a nie kiedy GC zdecyduje się to zrobić? Mam program do rozpoznawania twarzy, który potrzebuje , aby pozbyć się 530 MB wewnętrznych obrazów , ponieważ nie są już potrzebne. Kiedy tego nie robimy: maszyna staje w miejscu.

Bonus Reading

Dla każdego, kto lubi styl tej odpowiedzi (wyjaśniając dlaczego, więc jak staje się oczywiste), proponuję przeczytać rozdział pierwszy Don Box ' a Essential COM:

Na 35 stronach wyjaśnia problemy związane z używaniem obiektów binarnych i wymyśla COM na twoich oczach. Po uświadomieniu sobie dlaczego COM, Pozostałe 300 stron jest oczywistych i tylko szczegółowo opisuje implementację Microsoftu.

Myślę, że każdy programista, który kiedykolwiek miał do czynienia z obiektami lub COM powinien, w przynajmniej przeczytaj pierwszy rozdział. To najlepsze wytłumaczenie wszystkiego.

Dodatkowy Bonus Do Czytania

Kiedy wszystko, co wiesz, jest złe Eric Lippert

Dlatego bardzo trudno jest napisać poprawny finalizer, najlepszą radą, jaką mogę ci dać, to nie próbować.

 2684
Author: Ian Boyd,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-03-01 16:13:48

IDisposable jest często używany do wykorzystania instrukcji using i skorzystania z łatwego sposobu deterministycznego czyszczenia zarządzanych obiektów.

public class LoggingContext : IDisposable {
    public Finicky(string name) {
        Log.Write("Entering Log Context {0}", name);
        Log.Indent();
    }
    public void Dispose() {
        Log.Outdent();
    }

    public static void Main() {
        Log.Write("Some initial stuff.");
        try {
            using(new LoggingContext()) {
                Log.Write("Some stuff inside the context.");
                throw new Exception();
            }
        } catch {
            Log.Write("Man, that was a heavy exception caught from inside a child logging context!");
        } finally {
            Log.Write("Some final stuff.");
        }
    }
}
 68
Author: yfeldblum,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-02-11 18:20:41

Celem schematu usuwania jest zapewnienie mechanizmu oczyszczania zarówno zarządzanych, jak i niezarządzanych zasobów, a kiedy to nastąpi, zależy od sposobu wywoływania metody usuwania. W twoim przykładzie wykorzystanie zbytu nie oznacza w rzeczywistości żadnych działań związanych ze zbytem, ponieważ wyczyszczenie listy nie ma wpływu na zbyty zbiór. Podobnie, wywołania ustawiania zmiennych NA null również nie mają wpływu na GC.

Zapraszamy do zapoznania się z artykułem szczegóły jak zaimplementować wzór utylizacji, ale zasadniczo wygląda to tak:
public class SimpleCleanup : IDisposable
{
    // some fields that require cleanup
    private SafeHandle handle;
    private bool disposed = false; // to detect redundant calls

    public SimpleCleanup()
    {
        this.handle = /*...*/;
    }

    protected virtual void Dispose(bool disposing)
    {
        if (!disposed)
        {
            if (disposing)
            {
                // Dispose managed resources.
                if (handle != null)
                {
                    handle.Dispose();
                }
            }

            // Dispose unmanaged managed resources.

            disposed = true;
        }
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }
}

Najważniejszą metodą jest Dispose (bool), która w rzeczywistości działa w dwóch różnych okolicznościach:

  • disposing = = true: metoda została wywołana bezpośrednio lub pośrednio przez kod użytkownika. Zarządzane i niezarządzane zasoby mogą być utylizowane.
  • disposing = = false: metoda została wywołana przez runtime z wewnątrz finalizera i nie należy odwoływać się do innych obiektów. Tylko niezarządzane zasoby mogą być usuwane.

Problem z po prostu pozwalając GC zająć się czyszczeniem jest to, że nie masz rzeczywistej kontroli nad tym, kiedy GC uruchomi cykl zbierania (możesz zadzwonić GC.Collect(), ale naprawdę nie powinieneś), więc zasoby mogą zostać dłużej niż jest to konieczne. Pamiętaj, że wywołanie Dispose() w rzeczywistości nie powoduje cyklu zbierania lub w jakikolwiek sposób powoduje, że GC zbiera / zwalnia obiekt; po prostu dostarcza środków do więcej deterministycznie oczyść wykorzystane zasoby i powiedz GC, że to oczyszczenie zostało już wykonane.

Cały sens IDisposable i the disposable pattern nie polega na natychmiastowym uwolnieniu pamięci. Jedyny raz, gdy wezwanie do dysponowania będzie miało nawet szansę natychmiastowego uwolnienia pamięci, jest wtedy, gdy obsługuje on pozbywanie się = = fałszywego scenariusza i manipuluje niezarządzanymi zasobami. W przypadku kodu zarządzanego pamięć nie zostanie odzyskana, dopóki GC nie uruchomi cyklu zbierania, nad którym tak naprawdę nie masz kontroli (poza wywołaniem GC.Collect (), o czym już wspomniałem nie jest dobrym pomysłem).

Twój scenariusz nie jest tak naprawdę poprawny, ponieważ ciągi w. Net nie używają żadnych niezaangażowanych zasobów i nie implementują IDisposable, nie ma sposobu, aby zmusić je do " posprzątania."

 45
Author: Scott Dorman,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-02-11 21:23:14

Nie powinno być dalszych wywołań metod obiektu po wywołaniu Dispose (chociaż obiekt powinien tolerować dalsze wywołania Dispose). Dlatego przykład w pytaniu jest głupi. Jeśli zostanie wywołane polecenie Dispose, to sam obiekt może zostać wyrzucony. Więc użytkownik powinien po prostu odrzucić wszystkie odniesienia do tego całego obiektu (ustawić je na null) i wszystkie powiązane obiekty wewnętrzne do niego zostaną automatycznie oczyszczone.

Jeśli chodzi o ogólne Pytanie o zarządzane/niezarządzane i dyskusja w innych odpowiedziach, myślę, że każda odpowiedź na to pytanie musi zaczynać się od definicji niezarządzanego zasobu.

Sprowadza się to do tego, że istnieje funkcja, którą możesz wywołać, aby umieścić system w stanie, i jest inna funkcja, którą możesz wywołać, aby przywrócić go z tego stanu. W typowym przykładzie pierwszy może być funkcją zwracającą Uchwyt Pliku, a drugi może być wywołaniem CloseHandle.

Ale-i to jest klucz - mogą to być dowolne pasujące do siebie pary funkcji. Jeden buduje stan, drugi go niszczy. Jeśli stan został zbudowany, ale nie został jeszcze zburzony, wtedy istnieje instancja zasobu. Musisz zaaranżować rozcięcie we właściwym czasie - zasób nie jest zarządzany przez CLR. Jedynym automatycznie zarządzanym typem zasobów jest pamięć. Istnieją dwa rodzaje: GC i stos. Typy wartości są zarządzane przez stos (lub przez podłączenie jazdy wewnątrz typów referencyjnych), oraz typy referencyjne są zarządzane przez GC.

Funkcje te mogą powodować zmiany stanu, które mogą być dowolnie przeplatane lub mogą wymagać perfekcyjnego zagnieżdżenia. Zmiany stanu mogą mieć charakter threadsafe lub nie.

Spójrz na przykład w pytaniu Sprawiedliwości. Zmiany w wcięciach pliku dziennika muszą być idealnie zagnieżdżone, inaczej wszystko pójdzie nie tak. Również są one mało prawdopodobne, aby być threadsafe.

Możliwe jest złapanie przejażdżki za pomocą garbage collector, aby uzyskać niezarządzane zasoby posprzątane. Ale tylko wtedy, gdy funkcje zmiany stanu są wątkami i dwa stany mogą mieć okresy, które w jakikolwiek sposób nakładają się na siebie. Tak więc przykład zasobu Sprawiedliwości nie może mieć finalizatora! To nikomu nie pomoże.

Dla tego rodzaju zasobów, można po prostu zaimplementować IDisposable, bez finalizera. Finalizer jest absolutnie opcjonalny - musi być. Jest to wymawiane lub nawet nie wspominane w wielu książkach.

Następnie musisz użyć using oświadczenia, aby mieć jakąkolwiek szansę na zapewnienie wywołania Dispose. Jest to zasadniczo jak połączenie jazdy ze stosem(tak jak finalizer jest do GC, using jest do stosu).

Brakującą częścią jest to, że musisz ręcznie napisać Dispose i wywołać go na swoich polach i klasie bazowej. Programiści C++ / CLI nie muszą tego robić. Kompilator pisze je dla nich w większości przypadków.

Jest alternatywa, którą wolę dla stanów, które idealnie się zagnieżdżają i nie są wątkami (poza czymkolwiek innym, unikanie IDisposable oszczędza Ci problemu kłótni z kimś, kto nie może się oprzeć dodaniu finalizera do każdej klasy, która implementuje IDisposable).

Zamiast pisać klasę, piszesz funkcję. Funkcja przyjmuje delegata do wywołania:

public static void Indented(this Log log, Action action)
{
    log.Indent();
    try
    {
        action();
    }
    finally
    {
        log.Outdent();
    }
}

I wtedy prostym przykładem może być:

Log.Write("Message at the top");
Log.Indented(() =>
{
    Log.Write("And this is indented");

    Log.Indented(() =>
    {
        Log.Write("This is even more indented");
    });
});
Log.Write("Back at the outermost level again");

Przekazywana lambda służy jako blok kodu, więc to tak, jakbyś stworzył własną strukturę kontrolną, aby służyła temu samemu celowi, co using. nie ma już niebezpieczeństwa, że rozmówca go nadużywa. Nie ma mowy, żeby nie wyczyścili zasobów.

Ta technika jest mniej przydatna, jeśli zasób jest rodzajem, który może mieć nakładające się życia, ponieważ wtedy chcesz być w stanie zbudować zasób A, następnie zasób B, następnie zabić zasób A, a następnie zabić zasób B. nie możesz tego zrobić, jeśli zmusiłeś użytkownika do perfekcyjnego zagnieżdżenia w ten sposób. Ale potem musisz użyć IDisposable (ale nadal bez finalizera, chyba że masz zaimplementowane threadsafety, które nie są wolne).

 20
Author: Daniel Earwicker,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-02-23 10:01:11

Scenariusze wykorzystuję IDisposable: czyszczenie niezarządzanych zasobów, rezygnacja z subskrypcji wydarzeń, bliskie połączenia

Idiom, którego używam do implementacji IDisposable (a nie threadsafe):

class MyClass : IDisposable {
    // ...

    #region IDisposable Members and Helpers
    private bool disposed = false;

    public void Dispose() {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    private void Dispose(bool disposing) {
        if (!this.disposed) {
            if (disposing) {
                // cleanup code goes here
            }
            disposed = true;
        }
    }

    ~MyClass() {
        Dispose(false);
    }
    #endregion
}
 17
Author: olli-MSFT,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-10-18 11:33:20

Jeśli MyCollection i tak będą zbierane śmieci, to nie trzeba ich wyrzucać. W ten sposób procesor będzie bardziej niż to konieczne, a nawet może unieważnić wstępnie obliczoną analizę, którą garbage collector już wykonał.

Używam IDisposable, aby zapewnić prawidłowe usuwanie wątków wraz z niezarządzanymi zasobami.

EDIT W odpowiedzi na komentarz Scotta:

tylko wtedy, gdy GC performance metrics są dotyczy to, gdy wywołanie [sic] GC.Collect() jest wykonane "

Koncepcyjnie, GC utrzymuje widok grafu odniesienia obiektu i wszystkich odniesień do niego z ramek stosu wątków. Sterta ta może być dość duża i obejmować wiele stron pamięci. Jako optymalizacja, GC buforuje swoją analizę stron, które są mało prawdopodobne, aby często się zmieniać, aby uniknąć niepotrzebnego przeskanowania strony. GC otrzymuje powiadomienie od jądra, gdy dane na stronie ulegną zmianie, więc wie że strona jest brudna i wymaga przeskanowania. Jeśli kolekcja jest w Gen0, to jest prawdopodobne, że inne rzeczy na stronie również się zmieniają, ale jest to mniej prawdopodobne w Gen1 i Gen2. Anegdotycznie, te Hooki nie były dostępne w systemie Mac OS X dla zespołu, który przeportował GC na Mac, aby uzyskać wtyczkę Silverlight działającą na tej platformie.

Kolejny punkt przeciwko niepotrzebnej utylizacji zasobów: wyobraź sobie sytuację, w której Proces jest rozładowywany. Wyobraź sobie również, że proces ten ma biegałem od jakiegoś czasu. Są szanse, że wiele stron pamięci tego procesu zostało zamienionych na dysk. Przynajmniej nie są już w pamięci podręcznej L1 lub L2. W takiej sytuacji nie ma sensu, aby aplikacja, która rozładowuje, zamieniała wszystkie te dane i strony kodowe z powrotem do pamięci, aby "zwolnić" zasoby, które i tak zostaną zwolnione przez system operacyjny po zakończeniu procesu. Dotyczy to zarządzanych, a nawet niektórych niezarządzanych zasobów. Tylko zasoby, które zachowują wątki bez tła muszą być usunięte, w przeciwnym razie proces pozostanie żywy.

Teraz, podczas normalnego wykonywania są efemeryczne zasoby, które muszą być poprawnie wyczyszczone (jak wskazuje @fezmonkey połączenia z bazami danych, gniazda, klamki okien ), aby uniknąć niezarządzanych wycieków pamięci. Są to rzeczy, które należy usunąć. Jeśli utworzysz jakąś klasę, która posiada wątek (a przez owns rozumiem, że go utworzyła i dlatego jest odpowiedzialna za jego zapewnienie zatrzymuje się, przynajmniej według mojego stylu kodowania), wtedy ta klasa najprawdopodobniej musi zaimplementować IDisposable i zburzyć wątek podczas Dispose.

. NET Framework używa interfejsu IDisposable jako sygnału, a nawet ostrzeżenia dla programistów, że ta klasa musi zostać usunięta. Nie mogę wymyślić żadnych typów w frameworku, które implementują IDisposable (z wyjątkiem implementacji interfejsu jawnego), gdzie usuwanie jest opcjonalne.

 12
Author: Drew Noakes,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-02-12 09:34:24

Tak, ten kod jest całkowicie zbędny i niepotrzebny i nie sprawia, że garbage collector robi coś, czego nie zrobiłby inaczej (gdy instancja MyCollection wyjdzie poza zakres, czyli.) Zwłaszcza wywołania .Clear().

Odpowiedź na Twój edit: tak jakby. Jeśli to zrobię:

public void WasteMemory()
{
    var instance = new MyCollection(); // this one has no Dispose() method
    instance.FillItWithAMillionStrings();
}

// 1 million strings are in memory, but marked for reclamation by the GC

Jest funkcjonalnie identyczny do tego dla celów zarządzania pamięcią:

public void WasteMemory()
{
    var instance = new MyCollection(); // this one has your Dispose()
    instance.FillItWithAMillionStrings();
    instance.Dispose();
}

// 1 million strings are in memory, but marked for reclamation by the GC

Jeśli naprawdę musisz zwolnić pamięć w tej chwili, zadzwoń GC.Collect(). Nie ma powodu. żeby zrobić to tutaj. Pamięć zostanie uwolniona, gdy będzie potrzebna.

 11
Author: mqp,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-02-11 21:17:47

W przykładzie, który napisałeś, nadal nie "uwolni teraz pamięci". Cała pamięć jest zbierana ze śmieci, ale może pozwolić na gromadzenie pamięci we wcześniejszej generacji . Musiałbyś przeprowadzić kilka testów, żeby się upewnić.


Wytyczne dotyczące projektowania ram są wytycznymi, a nie regułami. Mówią ci, do czego służy przede wszystkim interfejs, kiedy go używać, jak go używać, a kiedy nie używać.

Kiedyś przeczytałem kod, który był prostym RollBack () po niepowodzeniu To możliwe. Poniższa Klasa MiniTx sprawdzi flagę Dispose () i jeśli wywołanie Commit nigdy się nie zdarzyło, wywoła Rollback sama. Dodano warstwę indrection, dzięki czemu kod wywołujący jest o wiele łatwiejszy do zrozumienia i utrzymania. Wynik wyglądał mniej więcej tak:

using( MiniTx tx = new MiniTx() )
{
    // code that might not work.

    tx.Commit();
} 

Widziałem też, jak kod czasu / logowania robi to samo. W tym przypadku metoda Dispose() zatrzymała timer i zapisała, że blok został zamknięty.

using( LogTimer log = new LogTimer("MyCategory", "Some message") )
{
    // code to time...
}
Oto kilka konkretnych przykładów które nie czyszczą zasobów niezarządzanych, ale z powodzeniem wykorzystują IDisposable do tworzenia czystszego kodu.
 7
Author: Robert Paulson,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-02-11 21:07:01

Jeśli chcesz usunąć teraz , Użyj pamięci niezarządzanej.

Zobacz:

 7
Author: franckspike,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-06-03 21:07:50

Nie będę powtarzał zwykłych rzeczy o używaniu lub uwalnianiu niewykorzystanych zasobów, które zostały już omówione. Ale chciałbym zwrócić uwagę na to, co wydaje się powszechnym błędnym przekonaniem.
Podano następujący kod

Public Class LargeStuff
  Implements IDisposable
  Private _Large as string()

  'Some strange code that means _Large now contains several million long strings.

  Public Sub Dispose() Implements IDisposable.Dispose
    _Large=Nothing
  End Sub

Zdaję sobie sprawę, że jednorazowe wdrożenie nie jest zgodne z aktualnymi wytycznymi, ale mam nadzieję, że wszyscy zrozumiecie pomysł.
Kiedy zostanie wywołany Dispose, ile pamięci zostanie uwolnionych?

Odpowiedź: Brak.
Wywołanie Dispose może zwolnić niezarządzane zasoby, nie może Odzyskaj pamięć zarządzaną, tylko GC może to zrobić. To nie znaczy, że powyższe nie jest dobrym pomysłem, przestrzeganie powyższego wzorca jest nadal dobrym pomysłem w rzeczywistości. Po uruchomieniu Dispose nic nie stoi na przeszkodzie, aby GC ponownie odebrało pamięć używaną przez _Large, mimo że instancja LargeStuff może nadal być w zasięgu. Łańcuchy w _Large mogą być również w gen 0, ale instancją LargeStuff może być gen 2, więc ponownie pamięć zostanie odebrana wcześniej.
Nie ma sensu w dodanie finalizera do wywołania metody Dispose pokazanej powyżej. To tylko opóźni odzyskanie pamięci, aby umożliwić uruchomienie finalizera.

 6
Author: pipTheGeek,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-02-11 21:08:56

Oprócz jego podstawowego zastosowania jako sposobu kontrolowania Życia z zasobów systemowych (całkowicie objętych niesamowitą odpowiedź Ian , kudos!), kombi IDisposable / using może być również użyte do scope zmiany stanu (krytycznych) zasobów globalnych : konsola , wątki , proces , dowolny obiekt globalJak instancja aplikacji.

Napisałem artykuł o tym wzorze: http://pragmateek.com/c-scope-your-global-state-changes-with-idisposable-and-the-using-statement/

Ilustruje, jak można chronić niektóre często używane Stany globalne w wielokrotnego użytkui czytelne sposób: kolory konsoli , bieżąca kultura wątków , Właściwości obiektu aplikacji Excel ...

 5
Author: Pragmateek,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-06-15 13:56:16

Jeśli już, to oczekuję, że kod będzie mniej efektywny niż przy pomijaniu go.

Wywołanie metod Clear () jest niepotrzebne, a GC prawdopodobnie nie zrobiłby tego, gdyby Dispose tego nie zrobił...

 4
Author: Arjan Einbu,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-02-11 20:32:25

Są rzeczy, które operacja Dispose() wykonuje w przykładowym kodzie, które mogą wywołać efekt, który nie wystąpiłby z powodu normalnego GC obiektu MyCollection.

Jeśli obiekty, do których odwołuje się _theList lub _theDict, są odwoływane przez inne obiekty, to ten List<> lub Dictionary<> Obiekt nie będzie podlegał kolekcji, ale nagle nie będzie miał zawartości. Gdyby nie było operacji Dispose (), jak w przykładzie, zbiory te nadal zawierałyby swoją zawartość.

Z oczywiście, gdyby taka była sytuacja, nazwałbym to zepsutym projektem - po prostu zaznaczam (pedantycznie, jak sądzę), że operacja Dispose() może nie być całkowicie zbędna, w zależności od tego, czy istnieją inne zastosowania List<> czy Dictionary<>, których nie pokazano we fragmencie.

 2
Author: Michael Burr,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-02-11 18:45:58

Problem w większości dyskusji na temat "niezarządzanych zasobów" polega na tym, że nie definiują One tego terminu, ale wydają się sugerować, że ma on coś wspólnego z niezarządzanym kodem. Chociaż prawdą jest, że wiele rodzajów niezarządzanych zasobów nie łączy się z niezarządzanym kodem, myślenie o niezarządzanych zasobach w takich terminach nie jest pomocne.

Zamiast tego należy rozpoznać, co wszystkie zarządzane zasoby mają ze sobą wspólnego: wszystkie pociągają za sobą obiekt proszący jakąś zewnętrzną "rzecz", aby zrobiła coś w jego imieniu, ze szkodą dla innych "rzeczy", a drugi podmiot zgadzający się na to do odwołania. Gdyby obiekt miał zostać porzucony i zniknąć bez śladu, nic nigdy nie powiedzie tej zewnętrznej 'rzeczy', że nie musi już zmieniać swojego zachowania w imieniu obiektu, który już nie istniał; w konsekwencji "przydatność tej' rzeczy zostałaby trwale zmniejszona.

Niezarządzany zasób reprezentuje więc porozumienie jakiejś zewnętrznej "rzeczy", aby zmienić jego zachowanie w imieniu przedmiotu, który nie byłby przydatny do użytku zewnętrznego "rzeczy", gdyby obiekt został porzucony i przestał istnieć. Zarządzany zasób to obiekt, który jest beneficjentem takiej umowy, ale który zarejestrował się, aby otrzymać powiadomienie, jeśli zostanie porzucone, i który wykorzysta takie powiadomienie, aby uporządkować swoje sprawy, zanim zostanie zniszczone.

 2
Author: supercat,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-02-22 06:38:32

IDisposable jest dobry do wypisania się z wydarzeń.

 2
Author: Adam Speight,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-11-22 15:16:35

Pierwszy z definicji. Dla mnie zasób niezarządzany oznacza jakąś klasę, która implementuje IDisposable interface lub coś stworzonego przy użyciu wywołań do dll. GC nie wie, jak radzić sobie z takimi obiektami. Jeśli klasa ma na przykład tylko typy wartości, to nie uważam tej klasy za klasę z niezarządzanymi zasobami. Dla mojego kodu stosuję kolejne praktyki:

  1. Jeśli stworzona przeze mnie Klasa używa jakichś niezarządzanych zasobów to znaczy, że powinienem również zaimplementować IDisposable interface w kolejności aby oczyścić pamięć.
  2. Wyczyść obiekty, gdy tylko skończyłem z nim korzystać.
  3. w mojej metodzie dispose iteruję nad wszystkimi IDisposable członkami klasy i wywołuję Dispose.
  4. In my Dispose method call GC.SuppressFinalize (this) W celu powiadomienia garbage collector, że mój obiekt został już wyczyszczony. Robię to, ponieważ wywołanie GC jest kosztowną operacją.
  5. jako dodatkowe zabezpieczenie staram się umożliwić wywołanie Dispose () multiple razy.
  6. czasami dodaję Private member _disposed i sprawdzam wywołania metody czy obiekt został wyczyszczony. A jeśli został wyczyszczony, Wygeneruj ObjectDisposedException
    Poniższy szablon demonstruje to, co opisałem słowami jako przykład kodu:

public class SomeClass : IDisposable
    {
        /// <summary>
        /// As usually I don't care was object disposed or not
        /// </summary>
        public void SomeMethod()
        {
            if (_disposed)
                throw new ObjectDisposedException("SomeClass instance been disposed");
        }

        public void Dispose()
        {
            Dispose(true);
        }

        private bool _disposed;

        protected virtual void Dispose(bool disposing)
        {
            if (_disposed)
                return;
            if (disposing)//we are in the first call
            {
            }
            _disposed = true;
        }
    }
 2
Author: Yuriy Zaletskyy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-08-31 12:18:20

Podany przykład kodu nie jest dobrym przykładem użycia IDisposable. Czyszczenie słownika normalnie nie powinno iść do metody Dispose. Pozycje słownika zostaną wyczyszczone i usunięte, gdy wyjdzie poza zakres. IDisposable implementacja jest wymagana do uwolnienia niektórych pamięci/programów obsługi, które nie będą uwalniane/wolne nawet po ich przekroczeniu.

Poniższy przykład pokazuje dobry przykład dla IDisposable pattern z kodem i komentarzami.

public class DisposeExample
{
    // A base class that implements IDisposable. 
    // By implementing IDisposable, you are announcing that 
    // instances of this type allocate scarce resources. 
    public class MyResource: IDisposable
    {
        // Pointer to an external unmanaged resource. 
        private IntPtr handle;
        // Other managed resource this class uses. 
        private Component component = new Component();
        // Track whether Dispose has been called. 
        private bool disposed = false;

        // The class constructor. 
        public MyResource(IntPtr handle)
        {
            this.handle = handle;
        }

        // Implement IDisposable. 
        // Do not make this method virtual. 
        // A derived class should not be able to override this method. 
        public void Dispose()
        {
            Dispose(true);
            // This object will be cleaned up by the Dispose method. 
            // Therefore, you should call GC.SupressFinalize to 
            // take this object off the finalization queue 
            // and prevent finalization code for this object 
            // from executing a second time.
            GC.SuppressFinalize(this);
        }

        // Dispose(bool disposing) executes in two distinct scenarios. 
        // If disposing equals true, the method has been called directly 
        // or indirectly by a user's code. Managed and unmanaged resources 
        // can be disposed. 
        // If disposing equals false, the method has been called by the 
        // runtime from inside the finalizer and you should not reference 
        // other objects. Only unmanaged resources can be disposed. 
        protected virtual void Dispose(bool disposing)
        {
            // Check to see if Dispose has already been called. 
            if(!this.disposed)
            {
                // If disposing equals true, dispose all managed 
                // and unmanaged resources. 
                if(disposing)
                {
                    // Dispose managed resources.
                    component.Dispose();
                }

                // Call the appropriate methods to clean up 
                // unmanaged resources here. 
                // If disposing is false, 
                // only the following code is executed.
                CloseHandle(handle);
                handle = IntPtr.Zero;

                // Note disposing has been done.
                disposed = true;

            }
        }

        // Use interop to call the method necessary 
        // to clean up the unmanaged resource.
        [System.Runtime.InteropServices.DllImport("Kernel32")]
        private extern static Boolean CloseHandle(IntPtr handle);

        // Use C# destructor syntax for finalization code. 
        // This destructor will run only if the Dispose method 
        // does not get called. 
        // It gives your base class the opportunity to finalize. 
        // Do not provide destructors in types derived from this class.
        ~MyResource()
        {
            // Do not re-create Dispose clean-up code here. 
            // Calling Dispose(false) is optimal in terms of 
            // readability and maintainability.
            Dispose(false);
        }
    }
    public static void Main()
    {
        // Insert code here to create 
        // and use the MyResource object.
    }
}
 2
Author: CharithJ,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-26 05:41:42

Widzę, że wiele odpowiedzi przesunęło się, aby mówić o używaniu IDisposable zarówno dla zarządzanych, jak i niezarządzanych zasobów. Sugerowałbym ten artykuł jako jedno z najlepszych wyjaśnień, jakie znalazłem dla tego, w jaki sposób należy właściwie używać IDisposable.

Https://www.codeproject.com/Articles/29534/IDisposable-What-Your-Mother-Never-Told-You-About

Dla właściwego pytania; jeśli używasz IDisposable do czyszczenia zarządzanych obiektów, które zajmują dużo pamięci, krótka odpowiedź byłoby nie . Powodem jest to, że po pozbyciu się IDisposable powinieneś pozwolić mu wyjść poza zakres. W tym momencie wszystkie odwołujące się obiekty potomne również są poza zakresem i zostaną zebrane.

Jedynym prawdziwym wyjątkiem od tego jest to, że masz dużo pamięci związanej w zarządzanych obiektach i zablokowałeś ten wątek czekając na zakończenie jakiejś operacji. Jeśli obiekty, które nie będą potrzebne po zakończeniu tego wywołania, to ustawienie tych odwołań do null może pozwolić garbage collector zebrać je wcześniej. Ale ten scenariusz reprezentowałby zły kod, który wymagał refakturowania - a nie przypadek użycia IDisposable.

 2
Author: MikeJ,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-10-03 17:32:35

Najbardziej uzasadnionym przypadkiem wykorzystania zasobów zarządzanych jest przygotowanie GC do odzyskania zasobów, które w przeciwnym razie nigdy nie zostałyby zebrane.

Pierwszym przykładem są odniesienia okrągłe.

Chociaż najlepszą praktyką jest używanie wzorców, które unikają odniesień okrągłych, jeśli skończysz z (na przykład) obiektem "child", który ma odniesienie z powrotem do swojego "rodzica", może to zatrzymać kolekcję GC rodzica, jeśli po prostu porzucisz odniesienie i polegasz na GC-plus, jeśli zaimplementowałeś finalizer, nigdy nie zostanie wywołany.

Jedynym sposobem jest ręczne złamanie odniesień okrągłych przez ustawienie referencji nadrzędnych na null na dzieciach.

Wdrożenie IDisposable na rodzica i dzieci jest najlepszym sposobem, aby to zrobić. Gdy Dispose jest wywoływane na Rodzicu, wywołaj Dispose na wszystkich dzieciach, a w metodzie Dispose Child Ustaw odwołania do rodzica na null.

 1
Author: controlbox,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-09-14 05:40:49