Ustawienie obiektu na wartość null vs Dispose ()


108

Fascynuje mnie sposób działania CLR i GC (pracuję nad poszerzaniem swojej wiedzy na ten temat czytając CLR w C #, książki / posty Jona Skeeta i nie tylko).

W każdym razie, jaka jest różnica między powiedzeniem:

MyClass myclass = new MyClass();
myclass = null;

Lub przez wprowadzenie MyClass do implementacji IDisposable i destruktora oraz wywołanie metody Dispose ()?

Ponadto, jeśli mam blok kodu z instrukcją using (np. Poniżej), jeśli przejdę przez kod i wyjdę z bloku using, to czy obiekt zostanie usunięty wtedy, czy też nastąpi wyrzucanie elementów bezużytecznych? Co by się stało, jeśli mimo wszystko wywołam Dispose () w bloku using?

using (MyDisposableObj mydispobj = new MyDisposableObj())
{

}

Klasy strumieniowe (np. BinaryWriter) mają metodę Finalize? Dlaczego miałbym tego używać?

Odpowiedzi:


210

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 usinginstrukcję, jest to po prostu cukier składniowy dla bloku try / final, więc Disposejest on wywoływany, nawet jeśli kod w treści usinginstrukcji 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ć, IDisposableilekroć twój typ "posiada" niezarządzany zasób, albo bezpośrednio (zwykle przez IntPtr), albo pośrednio (np. Poprzez a Stream, a SqlConnectionitp.).

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, FileStreamale zapomnisz wywołać Disposelub 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ą nulljest, 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 FileStreamzmienną 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 FileStreamposiadanie finalizatora (w razie potrzeby - może odnosić się do czegoś innego itp.). Jeśli chcesz mieć niezarządzany zasób „prawie” bezpośrednio, SafeHandlejesteś 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ść doSafeHandlenajszybciej 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 Disposecelu 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 :)


Re „Jedyny raz, w którym warto ustawić zmienną lokalną na wartość null” - być może także niektóre z bardziej drażliwych scenariuszy „przechwytywania” (wielokrotne przechwytywanie tej samej zmiennej) - ale nie warto komplikować postu! +1 ...
Marc Gravell

@Marc: To prawda - nawet nie pomyślałem o przechwyconych zmiennych. Hmm. Tak, myślę, że zostawię to w spokoju;)
Jon Skeet,

czy możesz powiedzieć, co się stanie, gdy ustawisz „foo = null” w powyższym fragmencie kodu? O ile wiem, ta linia czyści tylko wartość zmiennej wskazującej na obiekt foo w zarządzanej stercie? więc pytanie brzmi, co się tam stanie z obiektem foo? czy nie powinniśmy zadzwonić do pozbycia się tego?
odiseh

@odiseh: Jeśli obiekt byłby jednorazowego użytku, to tak - powinieneś go wyrzucić. Jednak ta część odpowiedzi dotyczyła tylko zbierania śmieci, co jest całkowicie oddzielne.
Jon Skeet,

1
Szukałem wyjaśnienia niektórych problemów, które można usunąć, więc wyszukałem w Google hasło „IDisposable Skeet” i znalazłem to. Wspaniały! : D
Maciej Woźniak

22

Usunięcie obiektu powoduje zwolnienie zasobów. Kiedy przypisujesz wartość null do zmiennej, po prostu zmieniasz odniesienie.

myclass = null;

Po wykonaniu tej czynności obiekt, do którego odnosiła się myklasa, nadal istnieje i będzie kontynuował, dopóki GC nie zajmie się jego czyszczeniem. Jeśli Dispose jest jawnie wywoływane lub znajduje się w bloku using, wszelkie zasoby zostaną zwolnione tak szybko, jak to możliwe.


7
To może nie istnieć nadal po wykonaniu tej linii - może to być śmieci zebrane przed tą linią. JIT jest sprytny - sprawia, że ​​takie linie są prawie zawsze nieistotne.
Jon Skeet

6
Ustawienie wartości null może oznaczać, że zasoby przechowywane przez obiekt nigdy nie zostaną zwolnione. GC nie usuwa, tylko finalizuje, więc jeśli obiekt bezpośrednio przechowuje niezarządzane zasoby, a jego finalizator nie usuwa (lub nie ma finalizatora), wówczas te zasoby wyciekną. Coś, o czym należy pamiętać.
LukeH

6

Te dwie operacje nie mają ze sobą wiele wspólnego. Kiedy ustawisz odwołanie na null, po prostu to robi. Samo w sobie nie ma to wpływu na klasę, do której się odwołano. Twoja zmienna po prostu nie wskazuje już obiektu, do którego była kiedyś, ale sam obiekt pozostaje niezmieniony.

Kiedy wywołujesz Dispose (), jest to wywołanie metody na samym obiekcie. Cokolwiek robi metoda Dispose, jest teraz wykonywane na obiekcie. Ale to nie wpływa na twoje odniesienie do obiektu.

Jedynym obszarem nakładania się jest to, że gdy nie ma już odniesień do obiektu, ostatecznie zostanie on usunięty. A jeśli klasa implementuje interfejs IDisposable, wówczas Dispose () zostanie wywołana na obiekcie, zanim zostanie on usunięty.

Ale nie stanie się to natychmiast po ustawieniu odwołania na null z dwóch powodów. Po pierwsze, mogą istnieć inne odwołania, więc w ogóle nie zostaną zebrane elementy bezużyteczne, a po drugie, nawet jeśli to było ostatnie odwołanie, więc jest teraz gotowe do zbierania elementów bezużytecznych, nic się nie stanie, dopóki moduł odśmiecania nie zdecyduje się usunąć obiekt.

Wywołanie metody Dispose () na obiekcie w żaden sposób nie „zabija” obiektu. Jest powszechnie używany do czyszczenia, aby obiekt można było później bezpiecznie usunąć, ale ostatecznie nie ma nic magicznego w Dispose, to tylko metoda klasowa.


Myślę, że ta odpowiedź komplementuje lub jest szczegółem odpowiedzi „rekurencyjnej”.
dance2die
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.