Ważne jest, aby oddzielić usuwanie od śmieci. Są to zupełnie odrębne sprawy, z jednym wspólnym punktem, do którego dojdę za chwilę.
Dispose
, wywóz śmieci i finalizacja
Kiedy piszesz using
instrukcję, jest to po prostu cukier składniowy dla bloku try / final, więc Dispose
jest on wywoływany, nawet jeśli kod w treści using
instrukcji zgłasza wyjątek. Nie oznacza to, że obiekt jest bezużyteczny na końcu bloku.
Usuwanie dotyczy niezarządzanych zasobów ( zasobów innych niż pamięć). Mogą to być uchwyty interfejsu użytkownika, połączenia sieciowe, uchwyty plików itp. Są to ograniczone zasoby, więc generalnie chcesz je zwolnić tak szybko, jak to możliwe. Powinieneś implementować, IDisposable
ilekroć twój typ "posiada" niezarządzany zasób, albo bezpośrednio (zwykle przez IntPtr
), albo pośrednio (np. Poprzez a Stream
, a SqlConnection
itp.).
Samo zbieranie śmieci dotyczy tylko pamięci - z jednym małym zwrotem akcji. Moduł odśmiecania pamięci jest w stanie znaleźć obiekty, do których nie można się już odwołać, i zwolnić je. Nie szuka jednak śmieci cały czas - tylko wtedy, gdy wykryje, że musi to zrobić (np. Jeśli zabraknie pamięci w jednej „generacji” sterty).
Twist to finalizacja . Odśmiecacz przechowuje listę obiektów, które nie są już osiągalne, ale które mają finalizator (napisany tak jak ~Foo()
w C #, nieco myląco - nie są one w niczym C ++ destruktorami). Uruchamia finalizatory na tych obiektach, na wypadek gdyby musieli wykonać dodatkowe czyszczenie, zanim ich pamięć zostanie zwolniona.
Finalizatory są prawie zawsze używane do czyszczenia zasobów w przypadku, gdy użytkownik tego typu zapomniał pozbyć się ich w uporządkowany sposób. Więc jeśli otworzysz, FileStream
ale zapomnisz wywołać Dispose
lub Close
, finalizator ostatecznie zwolni dla Ciebie uchwyt pliku źródłowego. W dobrze napisanym programie, moim zdaniem finalizatorzy prawie nigdy nie powinni odpalać.
Ustawienie zmiennej na null
Jeden mały punkt dotyczący ustawiania zmiennej na null
- prawie nigdy nie jest to wymagane ze względu na czyszczenie pamięci. Czasami możesz chcieć to zrobić, jeśli jest to zmienna składowa, chociaż z mojego doświadczenia wynika, że rzadko kiedy „część” obiektu nie jest już potrzebna. Kiedy jest to zmienna lokalna, JIT jest zwykle wystarczająco inteligentny (w trybie zwolnienia), aby wiedzieć, kiedy nie będziesz ponownie używać odwołania. Na przykład:
StringBuilder sb = new StringBuilder();
sb.Append("Foo");
string x = sb.ToString();
// The string and StringBuilder are already eligible
// for garbage collection here!
int y = 10;
DoSomething(y);
// These aren't helping at all!
x = null;
sb = null;
// Assume that x and sb aren't used here
Raz, gdzie mogą być warte ustawienie lokalną zmienną null
jest, gdy jesteś w pętli, a niektóre gałęzie konieczności pętli użyć zmiennej, ale wiesz, że osiągnęła punkt, w którym nie masz. Na przykład:
SomeObject foo = new SomeObject();
for (int i=0; i < 100000; i++)
{
if (i == 5)
{
foo.DoSomething();
// We're not going to need it again, but the JIT
// wouldn't spot that
foo = null;
}
else
{
// Some other code
}
}
Wdrażanie IDisposable / finalizers
Czy więc twoje własne typy powinny implementować finalizatory? Prawie na pewno nie. Jeśli tylko pośrednio przechowujesz niezarządzane zasoby (np. Masz FileStream
zmienną składową jako element członkowski), dodanie własnego finalizatora nie pomoże: strumień prawie na pewno będzie kwalifikował się do czyszczenia pamięci, gdy Twój obiekt jest, więc możesz po prostu polegać na FileStream
posiadanie finalizatora (w razie potrzeby - może odnosić się do czegoś innego itp.). Jeśli chcesz mieć niezarządzany zasób „prawie” bezpośrednio, SafeHandle
jesteś Twoim przyjacielem - rozpoczęcie pracy zajmuje trochę czasu, ale oznacza to, że prawie nigdy nie będziesz musiał ponownie pisać finalizatora . Zwykle finalizator powinien być potrzebny tylko wtedy, gdy masz naprawdę bezpośredni dostęp do zasobu (a IntPtr
) i powinieneś chcieć przejść doSafeHandle
najszybciej jak możesz. (Są tam dwa linki - najlepiej przeczytaj oba).
Joe Duffy ma bardzo długi zestaw wskazówek dotyczących finalizatorów i IDisposable (napisanych wspólnie z wieloma inteligentnymi ludźmi), które warto przeczytać. Warto mieć świadomość, że zapieczętowanie klas znacznie ułatwia życie: wzorzec zastępowania w Dispose
celu wywołania nowej Dispose(bool)
metody wirtualnej itp. Ma znaczenie tylko wtedy, gdy klasa jest przeznaczona do dziedziczenia.
To była trochę wędrówka, ale poproś o wyjaśnienie, gdzie chcesz :)