Ustawienie obiektów na wartość Null / Nothing po użyciu w .NET


187

Czy powinieneś ustawić wszystkie obiekty na null( Nothingw VB.NET) po ich zakończeniu?

Rozumiem, że w .NET bardzo ważne jest, aby pozbyć się wszelkich instancji obiektów implementujących IDisposableinterfejs w celu zwolnienia niektórych zasobów, chociaż obiekt może nadal być czymś po jego usunięciu (stąd isDisposedwłaściwość w formularzach), więc zakładam, że nadal może on rezydować w pamięci czy przynajmniej częściowo?

Wiem również, że gdy obiekt wykracza poza zakres, jest on następnie oznaczany do kolekcjonowania gotowy do następnego przejścia modułu wyrzucania elementów bezużytecznych (chociaż może to zająć trochę czasu).

Mając to na uwadze, czy ustawienie to nullprzyspieszy system zwalniający pamięć, ponieważ nie musi się zorientować, że nie jest już objęty zakresem i czy są to jakieś złe skutki uboczne?

Artykuły MSDN nigdy nie robią tego w przykładach i obecnie robię to, ponieważ nie widzę szkody. Jednak natknąłem się na mieszankę opinii, więc wszelkie komentarze są przydatne.


4
+1 świetne pytanie. Czy ktoś zna okoliczności, w których kompilator całkowicie zoptymalizuje przypisanie? tj. czy ktokolwiek spojrzał na MSIL w różnych okolicznościach i zauważył IL za ustawienie obiektu na zero (lub jego brak).
Tim Medora

Odpowiedzi:


73

Karl ma absolutną rację, po użyciu nie trzeba ustawiać obiektów na zero. Jeśli obiekt implementuje IDisposable, po prostu upewnij się, że dzwonisz, IDisposable.Dispose()gdy skończysz z tym obiektem (zawiniętym w try... finallylub using()blok). Ale nawet jeśli nie pamiętasz, aby zadzwonić Dispose(), metoda finalizatora obiektu powinna Dispose()cię wołać .

Myślałem, że to dobre traktowanie:

Kopanie w IDisposable

i to

Zrozumienie IDisposable

Nie ma sensu próbować zgadywać GC i jej strategii zarządzania, ponieważ jest to samostrojenie i nieprzejrzyste. Odbyła się dobra dyskusja na temat wewnętrznych działań z Jeffreyem Richterem na Dot Net Rocks: Jeffrey Richter na temat Windows Memory Model i książki Richters CLR poprzez C # rozdział 20 ma świetne podejście:


6
Reguła o braku ustawiania wartości null nie jest „twarda i szybka” ... jeśli obiekt zostanie umieszczony na stercie dużego obiektu (rozmiar> 85K), pomoże GC, jeśli ustawisz obiekt na null, gdy skończysz Użyj tego.
Scott Dorman,

Zgadzam się w ograniczonym zakresie, ale jeśli nie zaczniesz odczuwać presji pamięci, nie widzę potrzeby „przedwczesnej optymalizacji” poprzez ustawienie obiektów na wartość zerową po użyciu.
Kev

21
Cały ten proces „nie przedwcześnie optymalizuj” brzmi bardziej jak „Wolniej i nie martw się, ponieważ procesory stają się szybsze, a aplikacje CRUD i tak nie potrzebują prędkości”. Może to tylko ja. :)
BobbyShaftoe,

19
To tak naprawdę oznacza „Garbage Collector lepiej zarządza pamięcią niż ty”. To może być tylko ja. :)
BobRodes,

2
@BobbyShaftoe: Prawdopodobnie równie błędne jest stwierdzenie „przedwczesna optymalizacja jest zła, zawsze”, ponieważ przeskakuje do przeciwnej skrajności „brzmi bardziej jak„ wolniej ”. Żaden rozsądny programista też by tego nie powiedział. Chodzi o niuanse i mądre podejście do optymalizacji. Osobiście martwiłbym się klarownością kodu i NASTĘPNIE TESTOWĄ TESTOWĄ wydajnością, ponieważ osobiście widziałem wiele osób (w tym siebie, gdy byłem młodszy) spędza zdecydowanie zbyt dużo czasu na tworzeniu „idealnego” algorytmu, aby zaoszczędzić 0,1 ms w 100 000 iteracjach, podczas gdy czytelność została całkowicie zastrzelona.
Brent Rittenhouse

36

Innym powodem, dla którego należy unikać ustawiania obiektów na wartość NULL po ich zakończeniu, jest fakt, że mogą one dłużej utrzymywać je przy życiu.

na przykład

void foo()
{
    var someType = new SomeType();
    someType.DoSomething();
    // someType is now eligible for garbage collection         

    // ... rest of method not using 'someType' ...
}

pozwoli obiektowi określonemu przez someType zostać GC po wywołaniu „DoSomething”, ale

void foo()
{
    var someType = new SomeType();
    someType.DoSomething();
    // someType is NOT eligible for garbage collection yet
    // because that variable is used at the end of the method         

    // ... rest of method not using 'someType' ...
    someType = null;
}

może czasem utrzymywać obiekt przy życiu aż do końca metody. JIT będzie zwykle optymalizowana dala przyporządkowanie do zerową , więc w obu bitów kodu do góry koniec jest taki sam.


To interesujący punkt. Zawsze myślałem, że obiekty nie wykraczają poza zakres, dopóki metoda, w której zostały one określone, nie jest kompletna. O ile oczywiście obiekt nie ma zasięgu w bloku Using lub nie jest jawnie ustawiony na Nothing lub null.
Guru Josh

1
Preferowanym sposobem na utrzymanie ich przy życiu jest użycie GC.KeepAlive(someType); Zobacz ericlippert.com/2013/06/10/construction-destruction
NotMe

14

Nie, nie zeruj obiektów. Możesz sprawdzić http://codebetter.com/blogs/karlseguin/archive/2008/04/27/foundations-of-programming-pt-7-back-to-basics-memory.aspx, aby uzyskać więcej informacji, ale ustawienie rzeczy zerowanie nic nie zrobi, oprócz wyczyszczenia kodu.


1
Ładne i szczegółowe wyjaśnienie dotyczące pamięci we współdzielonym linku
user2323308

Uszkodzony link. Bez powiązanych treści ta odpowiedź jest raczej przydatna i powinna zostać usunięta.
Imre Pühvel

7

Również:

using(SomeObject object = new SomeObject()) 
{
  // do stuff with the object
}
// the object will be disposed of

7

Ogólnie rzecz biorąc, nie ma potrzeby zerowania obiektów po użyciu, ale w niektórych przypadkach uważam, że to dobra praktyka.

Jeśli obiekt implementuje IDisposable i jest przechowywany w polu, myślę, że warto go wyzerować, aby uniknąć użycia usuwanego obiektu. Błędy tego rodzaju mogą być bolesne:

this.myField.Dispose();
// ... at some later time
this.myField.DoSomething();

Dobrze jest zerować pole po jego usunięciu i uzyskać NullPtrEx bezpośrednio w wierszu, w którym pole jest ponownie używane. W przeciwnym razie możesz napotkać jakiś tajemniczy błąd wzdłuż linii (w zależności od tego, co dokładnie robi DoSomething).


8
Cóż, rozmieszczony obiekt powinien zgłosić wyjątek ObjectDisposedException, jeśli został już usunięty. O ile mi wiadomo, wymaga to całego kodu, ale z drugiej strony Disposed to i tak źle przemyślany paradygmat.
nicodemus13,

3
Ctrl + F dla .Dispose(). Jeśli go znajdziesz, nie używasz poprawnie IDisposable. Jedynym zastosowaniem dla obiektu jednorazowego użytku powinno być ograniczenie bloku użytkowego. A po użyciu bloku nie masz myFieldjuż nawet dostępu do niego . A w bloku używającym, ustawienie nullto nie jest wymagane, blok użytkowy zbywa obiekt za ciebie.
Suamere,

7

Możliwe, że twój kod nie jest wystarczająco ściśle skonstruowany, jeśli odczuwasz potrzebę nullzmiennych.

Istnieje wiele sposobów ograniczenia zakresu zmiennej:

Jak wspomniał Steve Tranby

using(SomeObject object = new SomeObject()) 
{
  // do stuff with the object
}
// the object will be disposed of

Podobnie możesz po prostu użyć nawiasów klamrowych:

{
    // Declare the variable and use it
    SomeObject object = new SomeObject()
}
// The variable is no longer available

Uważam, że używanie nawiasów klamrowych bez „nagłówka” w celu naprawdę wyczyszczenia kodu i uczynienia go bardziej zrozumiałym.


Raz próbowałem użyć niestandardowych lokalnych zasięgów (głównie smarta $$). Firma eksplodowała.
Suamere

Inna uwaga: Dzieje się tak, ponieważ kompilator c # znajdzie zmienne o zasięgu lokalnym, które implementują IDisposable, i wywoła funkcję .Dispose (WIĘCEJ czasu), gdy ich zakres się zakończy. Jednak ... Połączenia SQL są jednym wielkim czasem, gdy .Dispose () nigdy nie jest zoptymalizowane. Istnieje kilka rodzajów, które wymagają wyraźnej uwagi, więc osobiście zawsze robię rzeczy jawnie, aby się nie ugryźć.
Suamere

5

Jedynym momentem, w którym powinieneś ustawić zmienną na null, jest to, kiedy zmienna nie wykracza poza zakres i nie potrzebujesz już danych z nią powiązanych. W przeciwnym razie nie ma takiej potrzeby.


2
To prawda, ale oznacza to również, że prawdopodobnie powinieneś zreformować swój kod. Nie sądzę, żebym kiedykolwiek potrzebował zadeklarować zmienną poza zamierzonym zakresem.
Karl Seguin,

2
Jeśli rozumie się, że „zmienna” obejmuje pola obiektowe, ta odpowiedź ma wiele sensu. W przypadku, gdy „zmienna” oznacza tylko „lokalną zmienną” (metody), prawdopodobnie mówimy tutaj o przypadkach niszowych (np. Metoda, która działa o wiele dłużej niż zwykle).
stakx - nie przyczynia się już

5

Zasadniczo nie trzeba ustawiać wartości null. Załóżmy jednak, że masz w swojej klasie funkcję resetowania.

Następnie możesz to zrobić, ponieważ nie chcesz wywoływać funkcji usuwania dwukrotnie, ponieważ niektóre z funkcji Dispose mogą nie zostać poprawnie zaimplementowane i zgłoszą wyjątek System.ObjectDisposed.

private void Reset()
{
    if(_dataset != null)
    {
       _dataset.Dispose();
       _dataset = null;
    }
    //..More such member variables like oracle connection etc. _oraConnection
 }

Najlepiej po prostu wyśledzić to z osobną flagą.
Thulani Chivandikwa

3

tego rodzaju „nie ma potrzeby ustawiania obiektów na zero po użyciu” nie jest całkowicie dokładne. Czasami musisz NULL zmienną po jej usunięciu.

Tak, powinieneś ZAWSZE zadzwonić .Dispose()lub .Close()coś, co ma to po zakończeniu. Czy to uchwyty plików, połączenia z bazą danych czy obiekty jednorazowe.

Oddzielny jest bardzo praktyczny wzór LazyLoad.

Muszę powiedzieć i wystąpienia ObjAz class A. Class Ama własność publiczną o nazwie PropBod class B.

Wewnętrznie PropBużywa zmiennej prywatnej _Bi domyślnie ma wartość null. Gdy PropB.Get()jest używany, sprawdza, czy _PropBma wartość null, a jeśli tak, otwiera zasoby potrzebne do utworzenia instancji Bw _PropB. Następnie powraca _PropB.

Z mojego doświadczenia wynika, że ​​jest to naprawdę przydatna sztuczka.

Gdy pojawia się potrzeba zerowania, jeśli zresetujesz lub zmienisz A w taki sposób, że zawartość _PropBbyła potomkiem poprzednich wartości A, będziesz musiał usunąć i _PropBzerować, aby LazyLoad mógł zresetować, aby pobrać poprawną wartość JEŻELI kod wymaga tego.

Jeśli tylko to zrobisz _PropB.Dispose()i wkrótce potem spodziewasz się, że sprawdzenie LazyLoad zakończy się sukcesem, nie będzie to zero, a będziesz patrzeć na nieaktualne dane. W efekcie musisz go zerować po Dispose()prostu, aby się upewnić.

I pamiętaj, żeby to było inaczej, ale mam kod teraz wykazujące to zachowanie po Dispose()na zasadzie _PropBa poza wywołanie funkcji, które zrobił Dispose (a więc prawie poza zakresem), prywatna prop jeszcze nie jest zerowa, i wciąż są tam nieaktualne dane.

Ostatecznie zbywana właściwość zostanie unieważniona, ale z mojej perspektywy było to niedeterministyczne.

Głównym powodem, jak wspomina dbkk, jest to, że kontener nadrzędny ( ObjAz PropB) zachowuje instancję _PropBw zakresie, pomimo Dispose().


Dobry przykład pokazujący, jak ręczne ustawienie wartości zerowej oznacza bardziej krytyczny błąd dla osoby dzwoniącej, co jest dobrą rzeczą.
rzuca

1

W niektórych przypadkach sensowne jest odwołanie do wartości zerowej. Na przykład, gdy piszesz kolekcję - jak kolejka priorytetowa - i zgodnie z umową, nie powinieneś utrzymywać tych obiektów przy życiu dla klienta po tym, jak klient usunie je z kolejki.

Ale tego rodzaju rzeczy mają znaczenie tylko w długowiecznych kolekcjach. Jeśli kolejka nie przetrwa końca funkcji, w której została utworzona, ma to o wiele mniejsze znaczenie.

Ogólnie rzecz biorąc, naprawdę nie powinieneś się tym przejmować. Pozwól kompilatorowi i GC wykonywać swoje zadania, abyś mógł wykonać swoje.



1

Stephen Cleary bardzo dobrze wyjaśnia w tym poście: Czy należy ustawić zmienne na Null, aby wspomóc zbieranie śmieci?

Mówi:

Krótka odpowiedź dla niecierpliwych Tak, jeśli zmienna jest polem statycznym lub jeśli piszesz metodę policzalną (przy użyciu zwrotu z zysku) lub metodę asynchroniczną (przy użyciu asynchronizacji i czekania). W przeciwnym razie nie.

Oznacza to, że w zwykłych metodach (niewymiennych i niesynchronicznych) nie ustawiasz zmiennych lokalnych, parametrów metody ani pól instancji na null.

(Nawet jeśli implementujesz IDisposable.Dispose, nadal nie powinieneś ustawiać zmiennych na null).

Ważną rzeczą, którą powinniśmy rozważyć, są pola statyczne .

Pola statyczne są zawsze obiektami głównymi , więc są zawsze uważane za „żywe” przez śmieciarza. Jeśli pole statyczne odwołuje się do obiektu, który nie jest już potrzebny, należy ustawić go na wartość NULL, aby moduł czyszczenia pamięci traktował go jako kwalifikujący się do kolekcji.

Ustawienie pól statycznych na wartość NULL nie ma znaczenia, jeśli cały proces zostanie zamknięty. Cała kupa ma być w tym momencie zbierana, łącznie ze wszystkimi obiektami głównymi.

Wniosek:

Pola statyczne ; o to chodzi. Wszystko inne to strata czasu .


0

Wierzę, że z projektu implementatorów GC nie można przyspieszyć GC z zerowaniem. Jestem pewien, że wolą nie martwić się o jak / kiedy GC prowadzi - traktować go tak wszechobecne Istota ochrony i czuwa nad i dla ciebie ... (łuki głową w dół, podnosi pięść do nieba) .. .

Osobiście często jawnie ustawiam zmienne na null, kiedy skończę z nimi jako formę samokontroli. Nie deklaruję, używam, a potem ustawiam na zero później - zeruję natychmiast po tym, gdy nie są już potrzebne. Mówię wprost: „Oficjalnie skończyłem z tobą ... odejdź ...”

Czy konieczne jest anulowanie w języku GC? Nie. Czy to jest pomocne dla GC? Może tak, może nie, nie wiem na pewno, z założenia naprawdę nie mogę tego kontrolować i niezależnie od dzisiejszej odpowiedzi z tą wersją, przyszłe implementacje GC mogą zmienić odpowiedź poza moją kontrolą. Plus, jeśli / kiedy zoptymalizowane jest zerowanie, to niewiele więcej niż fantazyjny komentarz, jeśli chcesz.

Sądzę, że jeśli dzięki temu moje zamiary staną się bardziej zrozumiałe dla następnego biednego głupca, który pójdzie w moje ślady, a jeśli to „może” czasem pomóc GC, to warto. Głównie sprawia, że ​​czuję się schludnie i czysto, a Mongo lubi czuć się schludnie i czysto. :)

Patrzę na to w ten sposób: istnieją języki programowania, dzięki którym ludzie dają wyobrażenie o zamiarach i kompilatorowi zapytanie o zadanie do wykonania - kompilator konwertuje to żądanie na inny język (czasem kilka) dla procesora - procesor (y) może dać sygnał, jakiego języka użyłeś, ustawień twojej karty, komentarzy, akcentów stylistycznych, nazw zmiennych itp. - procesor dotyczy strumienia bitów, który informuje go o tym, jakie rejestry, kody operacyjne i lokalizacje pamięci mają się zmieniać. Wiele rzeczy napisanych w kodzie nie przekształca się w to, co zużywa CPU w podanej przez nas sekwencji. Nasz C, C ++, C #, Lisp, Babel, asembler lub cokolwiek to jest teoria, a nie rzeczywistość, napisane jako zestawienie pracy. To, co widzisz, nie jest tym, co dostajesz, nawet w języku asemblera.

Rozumiem, że sposób myślenia „niepotrzebnych rzeczy” (takich jak puste linie) „to nic innego jak szum i niepotrzebny kod”. To byłem ja wcześniej w mojej karierze; Całkowicie to rozumiem. W tym momencie pochylam się w kierunku tego, co czyni kod wyraźniejszym. To nie tak, że dodałem nawet 50 linii „szumu” do moich programów - to kilka linii tu czy tam.

Istnieją wyjątki od każdej reguły. W scenariuszach z pamięcią ulotną, pamięcią statyczną, warunkami wyścigu, singletonami, użyciem „przestarzałych” danych i tego rodzaju zgnilizny, to jest inne: POTRZEBUJESZ zarządzać własną pamięcią, blokować i unieważniać jako apropos, ponieważ pamięć nie jest częścią wszechświat GC'd - mam nadzieję, że wszyscy to rozumieją. Reszta czasu w językach GC to kwestia stylu, a nie konieczności lub gwarantowanego wzrostu wydajności.

Na koniec upewnij się, że rozumiesz, co kwalifikuje się do GC, a co nie; odpowiednio blokować, usuwać i unieważniać; woskować, woskować; wdech i wydech; i na wszystko inne mówię: jeśli to jest dobre, zrób to. Twój przebieg może się różnić ... tak jak powinien ...


0

Myślę, że przywrócenie wartości zerowej jest nieporządne. Wyobraź sobie scenariusz, w którym ustawiony jest teraz przedmiot, powiedzmy przez właściwość. Teraz jakoś fragment kodu przypadkowo wykorzystuje tę właściwość po usunięciu elementu, otrzymasz wyjątek o wartości zerowej, który wymaga dokładnego zbadania, co dokładnie się dzieje.

Wierzę, że jednorazowe ramy pozwolą na rzucenie ObjectDisposedException, co jest bardziej znaczące. Z tego powodu lepszym rozwiązaniem byłoby przywrócenie wartości zerowej.


-1

Niektóre obiekty .dispose()zakładają metodę, która wymusza usunięcie zasobu z pamięci.


11
Nie, nie ma; Dispose () nie zbiera obiektu - służy do deterministycznego czyszczenia, zwykle zwalniając niezarządzane zasoby.
Marc Gravell

1
Pamiętając, że determinizm dotyczy tylko zasobów zarządzanych, a nie niezarządzanych (tj. Pamięci)
nicodemus13,
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.