Czy musisz pozbyć się przedmiotów i ustawić je na zero?

310

Czy musisz pozbyć się obiektów i ustawić je na wartość zerową, czy też śmieciarz je wyczyści, gdy wyjdą poza zakres?

CJ7
źródło
4
Wygląda na to, że istnieje konsensus, że nie trzeba ustawiać obiektu na zero, ale czy należy wykonać Dispose ()?
CJ7
3
jak powiedział Jeff: codinghorror.com/blog/2009/01/…
tanathos
9
Moja rada jest zawsze dostępna, jeśli obiekt implementuje IDisposable. Użyj bloku za każdym razem. Nie rób założeń, nie pozostawiaj tego przypadkowi. Nie musisz jednak ustawiać wartości na zero. Obiekt właśnie wyszedł poza zakres.
Peter
11
@peter: Nie używaj bloków „korzystających” z serwerów proxy klienta WCF: msdn.microsoft.com/en-us/library/aa355056.aspx
nlawalker
9
JEDNAK MOŻESZ chcieć ustawić niektóre odwołania do null w swojej Dispose()metodzie! Jest to subtelna odmiana tego pytania, ale ważna, ponieważ obiekt usuwany nie może wiedzieć, czy „wychodzi poza zakres” (wywołanie Dispose()nie jest gwarancją). Więcej tutaj: stackoverflow.com/questions/6757048/…
Kevin P. Rice

Odpowiedzi:

239

Obiekty zostaną wyczyszczone, gdy nie będą już używane i gdy śmieciarz uzna to za odpowiednie. Czasami może być konieczne ustawienie obiektu null, aby wykroczył poza zakres (na przykład pole statyczne, którego wartości już nie potrzebujesz), ale ogólnie nie ma potrzeby ustawiania go null.

Jeśli chodzi o usuwanie przedmiotów, zgadzam się z @Andre. Jeśli obiekt jest IDisposable, dobrym pomysłem jest pozbycie się go, gdy nie jest już potrzebny, szczególnie jeśli obiekt używa niezarządzanych zasobów. Nie pozbywanie się niezarządzanych zasobów doprowadzi do wycieków pamięci .

Możesz użyć usinginstrukcji, aby automatycznie pozbyć się obiektu, gdy Twój program opuści zakres usinginstrukcji.

using (MyIDisposableObject obj = new MyIDisposableObject())
{
    // use the object here
} // the object is disposed here

Co jest funkcjonalnie równoważne z:

MyIDisposableObject obj;
try
{
    obj = new MyIDisposableObject();
}
finally
{
    if (obj != null)
    {
        ((IDisposable)obj).Dispose();
    }
}
Zach Johnson
źródło
4
Jeśli obj jest typem odniesienia, to w końcu blok jest równoważny z:if (obj != null) ((IDisposable)obj).Dispose();
Randy obsługuje Monikę
1
@Tuzo: Dzięki! Edytowane, aby to odzwierciedlić.
Zach Johnson
2
Jedna uwaga dotycząca IDisposable. Nieusunięcie obiektu zazwyczaj nie spowoduje wycieku pamięci w żadnej dobrze zaprojektowanej klasie. Podczas pracy z niezarządzanymi zasobami w języku C # powinieneś mieć finalizator, który nadal zwalnia niezarządzane zasoby. Oznacza to, że zamiast zwalniać zasoby, kiedy należy to zrobić, zostanie on odroczony do momentu, gdy moduł odśmiecający sfinalizuje zarządzany obiekt. Nadal może jednak powodować wiele innych problemów (takich jak niepublikowane blokady). Powinieneś się IDisposablejednak pozbyć !
Aidiakapi
@RandyLevy Czy masz do tego referencje? Dzięki
Basic
Ale moje pytanie brzmi: Dispose () musi implementować logikę? Czy to musi coś zrobić? Lub wewnętrznie, gdy Dispose () nazywa się sygnałami GC, że warto iść? Sprawdziłem na przykład kod źródłowy TextWriter i Dispose nie ma implementacji.
Mihail Georgescu
137

Obiekty nigdy nie wychodzą poza zakres w języku C #, podobnie jak w języku C ++. Zajmuje się nimi Garbage Collector automatycznie, gdy nie są już używane. Jest to bardziej skomplikowane podejście niż C ++, w którym zakres zmiennej jest całkowicie deterministyczny. Moduł śmieciowy CLR aktywnie przechodzi przez wszystkie utworzone obiekty i sprawdza, czy są one używane.

Obiekt może wyjść „poza zakres” w jednej funkcji, ale jeśli jego wartość zostanie zwrócona, GC sprawdzi, czy funkcja wywołująca zachowuje wartość zwracaną.

Ustawienie odwołań do obiektu na null jest konieczne, ponieważ czyszczenie pamięci działa, sprawdzając, do których obiektów odwołują się inne obiekty.

W praktyce nie musisz się martwić zniszczeniem, po prostu działa i jest świetne :)

Disposenależy wywoływać we wszystkich obiektach, które implementują IDisposablepo zakończeniu pracy z nimi. Zwykle używałbyś usingbloku z takimi obiektami:

using (var ms = new MemoryStream()) {
  //...
}

EDYTOWAĆ W zakresie zmiennym. Craig zapytał, czy zakres zmiennej ma jakikolwiek wpływ na czas życia obiektu. Aby poprawnie wyjaśnić ten aspekt CLR, muszę wyjaśnić kilka pojęć z C ++ i C #.

Rzeczywisty zakres zmiennej

W obu językach zmienna może być używana tylko w tym samym zakresie, w jakim została zdefiniowana - klasa, funkcja lub blok instrukcji ujęty w nawiasy klamrowe. Subtelna różnica polega jednak na tym, że w języku C # zmienne nie mogą być ponownie zdefiniowane w zagnieżdżonym bloku.

W C ++ jest to całkowicie legalne:

int iVal = 8;
//iVal == 8
if (iVal == 8){
    int iVal = 5;
    //iVal == 5
}
//iVal == 8

W języku C # pojawia się błąd kompilatora:

int iVal = 8;
if(iVal == 8) {
    int iVal = 5; //error CS0136: A local variable named 'iVal' cannot be declared in this scope because it would give a different meaning to 'iVal', which is already used in a 'parent or current' scope to denote something else
}

Ma to sens, jeśli spojrzysz na wygenerowany MSIL - wszystkie zmienne używane przez funkcję są zdefiniowane na początku funkcji. Spójrz na tę funkcję:

public static void Scope() {
    int iVal = 8;
    if(iVal == 8) {
        int iVal2 = 5;
    }
}

Poniżej znajduje się wygenerowana IL. Zauważ, że iVal2, który jest zdefiniowany wewnątrz bloku if, jest faktycznie zdefiniowany na poziomie funkcji. W efekcie oznacza to, że C # ma zasięg klasy i funkcji tylko w zakresie zmiennego czasu życia.

.method public hidebysig static void  Scope() cil managed
{
  // Code size       19 (0x13)
  .maxstack  2
  .locals init ([0] int32 iVal,
           [1] int32 iVal2,
           [2] bool CS$4$0000)

//Function IL - omitted
} // end of method Test2::Scope

Zakres C ++ i czas życia obiektu

Ilekroć zmienna C ++ przydzielona na stosie wykracza poza zakres, zostaje zniszczona. Pamiętaj, że w C ++ możesz tworzyć obiekty na stosie lub na stercie. Kiedy tworzysz je na stosie, gdy wykonanie opuści zakres, są one wyskakiwane ze stosu i niszczone.

if (true) {
  MyClass stackObj; //created on the stack
  MyClass heapObj = new MyClass(); //created on the heap
  obj.doSomething();
} //<-- stackObj is destroyed
//heapObj still lives

Kiedy obiekty C ++ są tworzone na stercie, muszą zostać jawnie zniszczone, w przeciwnym razie jest to wyciek pamięci. Jednak nie ma takiego problemu ze zmiennymi stosu.

C # Żywotność obiektu

W CLR obiekty (tj. Typy referencyjne) są zawsze tworzone na zarządzanej stercie. Jest to dodatkowo wzmocnione przez składnię tworzenia obiektów. Rozważ ten fragment kodu.

MyClass stackObj;

W C ++ spowoduje to utworzenie instancji na MyClassstosie i wywołanie jej domyślnego konstruktora. W języku C # tworzyłoby odwołanie do klasy, MyClassktóre niczego nie wskazuje. Jedynym sposobem utworzenia instancji klasy jest użycie newoperatora:

MyClass stackObj = new MyClass();

W pewien sposób obiekty C # są bardzo podobne do obiektów tworzonych przy użyciu newskładni w C ++ - są tworzone na stercie, ale w przeciwieństwie do obiektów C ++, są zarządzane przez środowisko wykonawcze, więc nie musisz się martwić o ich zniszczenie.

Ponieważ obiekty są zawsze na stercie, fakt, że odwołania do obiektów (tj. Wskaźniki) wykraczają poza zakres, staje się dyskusyjny. Przy ustalaniu, czy obiekt ma zostać zebrany, bierze się więcej czynników niż po prostu obecność odniesień do obiektu.

C # Odwołania do obiektów

Jon Skeet porównał odwołania do obiektów w Javie do kawałków łańcucha przymocowanych do balonu, którym jest obiekt. Ta sama analogia dotyczy odwołań do obiektów C #. Wskazują po prostu lokalizację stosu zawierającego obiekt. Zatem ustawienie wartości null nie ma bezpośredniego wpływu na czas życia obiektu, balon nadal istnieje, dopóki GC go nie „wyskoczy”.

Kontynuując analogię balonu, wydaje się logiczne, że gdy balon nie będzie do niego przymocowany, może zostać zniszczony. W rzeczywistości dokładnie tak działają obiekty zliczane referencjami w językach niezarządzanych. Tyle że to podejście nie działa zbyt dobrze w przypadku odwołań cyklicznych. Wyobraź sobie dwa balony, które są połączone sznurkiem, ale żaden balon nie ma sznurka do niczego innego. Zgodnie z prostymi regułami liczenia referencji oba nadal istnieją, mimo że cała grupa balonów jest „osierocona”.

Obiekty .NET przypominają balony helowe pod dachem. Gdy dach się otworzy (biegnie GC) - nieużywane balony odpływają, nawet jeśli grupy balonów są ze sobą powiązane.

.NET GC wykorzystuje kombinację generacyjnego GC oraz mark i sweep. Podejście generacyjne obejmuje środowisko wykonawcze faworyzujące obiekty, które zostały ostatnio przydzielone, ponieważ są one bardziej nieużywane, a oznaczanie i przeglądanie obejmuje środowisko wykonawcze przechodzące przez cały wykres obiektów i sprawdzanie, czy istnieją grupy obiektów, które nie są używane. To odpowiednio rozwiązuje problem zależności cyklicznej.

Ponadto .NET GC działa na innym wątku (tak zwanym wątku finalizatora), ponieważ ma sporo do zrobienia, a wykonanie tego w głównym wątku zakłóciłoby działanie programu.

Igor Zevaka
źródło
1
@Igor: Przez wyjście poza zakres rozumiem, że odwołanie do obiektu jest poza kontekstem i nie można do niego odwoływać się w bieżącym zakresie. Z pewnością nadal tak się dzieje w języku C #.
CJ7
@Craig Johnston, nie myl zakresu zmiennych wykorzystywanego przez kompilator ze zmiennym czasem życia, który jest określony przez środowisko uruchomieniowe - są różne. Zmienna lokalna może nie być „aktywna”, nawet jeśli nadal ma zasięg.
Randy obsługuje Monikę
1
@Craig Johnston: patrz blogs.msdn.com/b/ericgu/archive/2004/07/23/192842.aspx : „nie ma gwarancji, że zmienna lokalna pozostanie aktywna do końca zakresu, jeśli nie jest Środowisko wykonawcze może analizować kod, który posiada, i ustalić, jakie są dalsze zastosowania zmiennej poza określonym punktem, a zatem nie utrzymywać tej zmiennej poza tym punktem (tj. nie traktować jej jako źródła do celów GC). ”
Randy obsługuje Monikę
1
@Tuzo: Prawda. Po to jest GC.KeepAlive.
Steven Sudit
1
@Craig Johnston: Nie i tak. Nie, ponieważ środowisko uruchomieniowe .NET zarządza tym za Ciebie i wykonuje dobrą robotę. Tak, ponieważ zadaniem programisty nie jest pisanie kodu, który (tylko) kompiluje, ale pisanie kodu, który działa . Czasami pomaga wiedzieć, co robi środowisko wykonawcze pod osłonami (np. Rozwiązywanie problemów). Można argumentować, że jest to wiedza, która pomaga oddzielić dobrych programistów od wielkich programistów.
Randy obsługuje Monikę
18

Jak powiedzieli inni, zdecydowanie chcesz zadzwonić, Disposejeśli klasa implementuje IDisposable. Zajmuję w tej sprawie dość sztywne stanowisko. Niektórzy mogą twierdzić, że Disposena DataSetprzykład wezwanie jest bezcelowe, ponieważ rozmontowali go i zobaczyli, że nie zrobił nic sensownego. Ale myślę, że w tym argumencie jest wiele błędów.

Przeczytaj to, aby poznać interesującą debatę szanowanych osób na ten temat. Następnie przeczytaj moje rozumowanie , dlaczego uważam, że Jeffery Richter jest w niewłaściwym obozie.

Teraz, czy powinieneś ustawić odniesienie do null. Odpowiedź brzmi nie. Pozwól mi zilustrować mój punkt za pomocą następującego kodu.

public static void Main()
{
  Object a = new Object();
  Console.WriteLine("object created");
  DoSomething(a);
  Console.WriteLine("object used");
  a = null;
  Console.WriteLine("reference set to null");
}

Kiedy zatem uważasz, że obiekt, do którego się odwołuje, akwalifikuje się do kolekcji? Jeśli powiedziałeś po wezwaniu a = null, to się mylisz. Jeśli powiedziałeś po zakończeniu Mainmetody, to również się mylisz. Prawidłowa odpowiedź jest taka, że ​​można ją odebrać w trakcie połączenia z DoSomething. Zgadza się. Kwalifikuje się, zanim odniesienie zostanie ustawione na, nulla może nawet przed zakończeniem wezwania do DoSomethingzakończenia. Wynika to z faktu, że kompilator JIT może rozpoznać, że odwołania do obiektów nie są już usuwane z dereferencji, nawet jeśli nadal są zrootowane.

Brian Gideon
źródło
3
Co jeśli apole prywatne jest członkiem klasy? Jeśli anie jest ustawiony na zero, GC nie ma możliwości dowiedzenia się, czy azostanie użyty ponownie w jakiejś metodzie, prawda? W ten sposób anie zostaną zebrane, dopóki nie zostanie zebrana cała klasa zawierająca. Nie?
Kevin P. Rice
4
@Kevin: Poprawnie. Gdyby abył członkiem klasy, a klasa zawierająca abyłaby nadal zrootowana i używana, to i tak by się trzymała. To jest jeden scenariusz, w którym ustawienie go nullmoże być korzystne.
Brian Gideon,
1
Twój punkt widzenia wiąże się z powodem, dla którego Disposejest ważny - nie można wywoływać Dispose(ani żadnej innej metody niewłączalnej) obiektu bez odniesienia do niego; wywołanie Disposepo zakończeniu pracy przy użyciu obiektu zagwarantuje, że referencja zrootowana będzie istnieć przez czas trwania ostatniej wykonanej na niej akcji. Porzucenie wszystkich odwołań do obiektu bez wywoływania Disposemoże ironicznie spowodować, że zasoby obiektu zostaną czasem zwolnione zbyt wcześnie .
supercat
Ten przykład i wyjaśnienie nie wydają się ostateczne w przypadku twardej sugestii, aby nigdy nie ustawiać odwołań na wartość null. Chodzi mi o to, z wyjątkiem komentarza Kevina, że ​​odwołanie ustawione na zero po wydaniu wydaje się dość łagodne , więc jaka szkoda? Czy coś brakuje?
dathompson
13

Nigdy nie musisz ustawiać obiektów na zero w języku C #. Kompilator i środowisko wykonawcze zajmą się ustaleniem, kiedy nie są już w zasięgu.

Tak, powinieneś pozbyć się obiektów, które implementują IDisposable.

EMP
źródło
2
Jeśli masz długotrwałe (lub nawet statyczne) odniesienie do dużego obiektu, musisz wantgo unieważnić, jak tylko skończysz, aby można go było odzyskać.
Steven Sudit
12
Jeśli kiedykolwiek „skończysz z tym”, nie powinno być statyczne. Jeśli nie jest statyczny, ale „długowieczny”, powinien zniknąć z zasięgu wkrótce po tym. Konieczność ustawienia odwołań na wartość null wskazuje na problem ze strukturą kodu.
EMP
Możesz mieć statyczny przedmiot, z którym się skończysz. Rozważ: Zasób statyczny, który jest odczytywany z dysku w formacie przyjaznym dla użytkownika, a następnie analizowany w formacie odpowiednim do użycia przez program. Możesz skończyć z prywatną kopią surowych danych, która nie służy już żadnemu celowi. (Przykład ze świata rzeczywistego: parsowanie jest procedurą dwuprzebiegową i dlatego nie może po prostu przetwarzać danych podczas ich odczytywania.)
Loren Pechtel
1
Następnie nie powinno przechowywać żadnych nieprzetworzonych danych w polu statycznym, jeśli jest używane tylko tymczasowo. Oczywiście, możesz to zrobić, po prostu nie jest to dobra praktyka z tego właśnie powodu: musisz ręcznie zarządzać jego żywotnością.
EMP
2
Można tego uniknąć, przechowując surowe dane w zmiennej lokalnej w metodzie, która je przetwarza. Metoda zwraca przetworzone dane, które przechowujesz, ale lokalny dla danych surowych wykracza poza zakres, gdy metoda kończy działanie i jest automatycznie GCed.
EMP
11

Zgadzam się ze wspólną odpowiedzią tutaj, że tak, powinieneś się pozbyć i nie, ogólnie nie powinieneś ustawiać zmiennej na zero ... ale chciałem zauważyć, że utylizacja NIE dotyczy przede wszystkim zarządzania pamięcią. Tak, może pomóc (a czasem pomaga) w zarządzaniu pamięcią, ale jego głównym celem jest zapewnienie deterministycznego uwolnienia ograniczonych zasobów.

Na przykład, jeśli otworzysz port sprzętowy (na przykład szeregowy), gniazdo TCP / IP, plik (w trybie wyłącznego dostępu) lub nawet połączenie z bazą danych, uniemożliwisz innym kodom korzystanie z tych elementów do momentu ich zwolnienia. Dispose zazwyczaj wypuszcza te elementy (wraz z GDI i innymi uchwytami „os” itp., Których jest 1000 dostępnych, ale ogólnie są ograniczone). Jeśli nie wywołasz polecenia dipose na obiekcie właściciela i jawnie zwolnisz te zasoby, spróbuj ponownie otworzyć ten sam zasób w przyszłości (lub inny program to zrobi), ta próba otwarcia zakończy się niepowodzeniem, ponieważ Twój nienarzucony, niepobrany obiekt nadal ma otwarty element . Oczywiście, gdy GC zbierze przedmiot (jeśli wzorzec Dispose został zaimplementowany poprawnie) zasób zostanie zwolniony ... ale nie wiesz, kiedy to będzie, więc nie t wiedzieć, kiedy można bezpiecznie ponownie otworzyć ten zasób. Jest to podstawowy problem, z którego działa Dispose. Oczywiście, zwolnienie tych uchwytów często zwalnia pamięć i nigdy ich nie zwolnienie może nigdy nie zwolnić tej pamięci ... stąd cała mowa o wyciekach pamięci lub opóźnieniach w czyszczeniu pamięci.

Widziałem przykłady tego powodujące problemy w świecie rzeczywistym. Na przykład widziałem aplikacje sieci Web ASP.Net, które ostatecznie nie mogą połączyć się z bazą danych (choć przez krótki czas lub do momentu ponownego uruchomienia procesu serwera WWW), ponieważ pula połączeń serwera SQL jest pełna ... , tak wiele połączeń zostało utworzonych i nie zostało jawnie zwolnionych w tak krótkim czasie, że nie można utworzyć nowych połączeń, a wiele połączeń w puli, chociaż nie jest aktywnych, nadal jest przywoływanych przez niepozbyte i niepobrane obiekty, dlatego też „ zostać ponownie wykorzystane. Prawidłowe usunięcie połączeń z bazą danych tam, gdzie to konieczne, zapewnia, że ​​ten problem nie wystąpi (przynajmniej nie, jeśli masz bardzo wysoki równoczesny dostęp).

Yort
źródło
11

Jeśli obiekt implementuje IDisposable, to tak, należy go usunąć. Obiekt może zawieszać się na zasobach macierzystych (uchwyty plików, obiekty systemu operacyjnego), które w innym przypadku mogą nie zostać natychmiast zwolnione. Może to prowadzić do głodu zasobów, problemów z blokowaniem plików i innych subtelnych błędów, których można by uniknąć.

Zobacz także Implementowanie metody usuwania w MSDN.

Chris Schmich
źródło
Ale czy kolektor garaży nie wywoła Dispose ()? Jeśli tak, to dlaczego miałbyś to nazywać?
CJ7
O ile nie zadzwonisz jawnie, nie ma gwarancji, że Disposezostanie wywołany. Ponadto, jeśli twój obiekt trzyma ograniczony zasób lub blokuje jakiś zasób (np. Plik), to chcesz go zwolnić jak najszybciej. Oczekiwanie, aż GC to zrobi, jest nieoptymalne.
Chris Schmich
12
GC nigdy nie wywoła Dispose (). GC może wezwać finalizatora, który zgodnie z konwencją powinien oczyścić zasoby.
adrianm
@adrianm: Nie mightdzwoń, ale willdzwoń.
leppie
2
@leppie: finalizatory nie są deterministyczne i mogą nie zostać wywołane (np. gdy aplikacja jest rozładowana). Jeśli potrzebujesz deterministycznej finalizacji, musisz wdrożyć coś, co moim zdaniem nazywa się krytycznym modułem obsługi. CLR ma specjalną obsługę tych obiektów, aby zagwarantować, że zostaną sfinalizowane (np. Wstępnie blokuje kod finalizacji, aby obsłużyć mało pamięci)
adrianm
9

Jeśli implementują interfejs IDisposable, należy je zutylizować. Śmieciarka zajmie się resztą.

EDYCJA: najlepiej jest użyć usingpolecenia podczas pracy z przedmiotami jednorazowymi:

using(var con = new SqlConnection("..")){ ...
Andre
źródło
5

Kiedy obiekt się implementuje IDisposable, powinieneś wywołać Dispose(lub Close, w niektórych przypadkach, wywoła to dla ciebie Dispose).

Zwykle nie musisz ustawiać obiektów null, ponieważ GC będzie wiedział, że obiekt nie będzie już używany.

Jest jeden wyjątek, kiedy ustawiam obiekty na null. Kiedy pobieram wiele obiektów (z bazy danych), nad którymi muszę pracować, i przechowuję je w kolekcji (lub tablicy). Kiedy „praca” jest wykonywana, ustawiam obiekt na null, ponieważ GC nie wie, że skończyłem z nim pracować.

Przykład:

using (var db = GetDatabase()) {
    // Retrieves array of keys
    var keys = db.GetRecords(mySelection); 

    for(int i = 0; i < keys.Length; i++) {
       var record = db.GetRecord(keys[i]);
       record.DoWork();
       keys[i] = null; // GC can dispose of key now
       // The record had gone out of scope automatically, 
       // and does not need any special treatment
    }
} // end using => db.Dispose is called
GvS
źródło
4

Zwykle nie trzeba ustawiać pól na zero. Zawsze jednak zalecałbym pozbywanie się niezarządzanych zasobów.

Z doświadczenia radzę również wykonać następujące czynności:

  • Zrezygnuj z subskrypcji wydarzeń, jeśli już ich nie potrzebujesz.
  • Ustaw dowolne pole z delegatem lub wyrażeniem na null, jeśli nie jest już potrzebne.

Natknąłem się na bardzo trudne do znalezienia problemy, które były bezpośrednim wynikiem nieprzestrzegania powyższych rad.

Dobrym miejscem do tego jest Dispose (), ale wcześniej jest zwykle lepiej.

Ogólnie rzecz biorąc, jeśli istnieje odwołanie do obiektu, moduł czyszczenia pamięci (GC) może potrwać kilka pokoleń dłużej, aby stwierdzić, że obiekt nie jest już używany. Cały czas obiekt pozostaje w pamięci.

To może nie stanowić problemu, dopóki nie zauważysz, że Twoja aplikacja używa dużo więcej pamięci, niż można się spodziewać. Gdy tak się stanie, podłącz profiler pamięci, aby zobaczyć, które obiekty nie są czyszczone. Ustawienie pól odwołujących się do innych obiektów do wartości zerowej i usuwanie zbędnych zbiorów może naprawdę pomóc GC dowiedzieć się, jakie obiekty można usunąć z pamięci. GC szybciej odzyska zużytą pamięć, dzięki czemu Twoja aplikacja będzie mniej głodna i szybsza.

Marnix van Valen
źródło
1
Co masz na myśli mówiąc o „wydarzeniach i delegatach” - co należy z nimi „wyczyścić”?
CJ7
@Craig - Zredagowałem swoją odpowiedź. Mam nadzieję, że to trochę to wyjaśni.
Marnix van Valen
3

Zawsze nazywaj utylizacją. To nie jest warte ryzyka. Duże zarządzane aplikacje korporacyjne powinny być traktowane z szacunkiem. Nie można poczynić żadnych założeń, bo wróci cię ugryźć.

Nie słuchaj leppie.

Wiele obiektów tak naprawdę nie implementuje IDisposable, więc nie musisz się o nie martwić. Jeśli rzeczywiście wykroczą poza zakres, zostaną automatycznie uwolnieni. Nigdy też nie spotkałem się z sytuacją, w której musiałem ustawić coś na zero.

Jedną rzeczą, która może się zdarzyć, jest to, że wiele przedmiotów może być otwartych. Może to znacznie zwiększyć wykorzystanie pamięci przez aplikację. Czasami trudno jest ustalić, czy to w rzeczywistości wyciek pamięci, czy też aplikacja robi po prostu wiele rzeczy.

Narzędzia profilu pamięci mogą pomóc w takich sprawach, ale może być trudne.

Ponadto zawsze anuluj subskrypcję niepotrzebnych wydarzeń. Uważaj również na wiązanie i kontrolki WPF. Nie jest to zwykła sytuacja, ale natknąłem się na sytuację, w której miałem kontrolę WPF, która była związana z obiektem leżącym u podstaw. Podstawowy obiekt był duży i zajmował dużą ilość pamięci. Kontrolkę WPF zastępowano nową instancją, a stara z jakiegoś powodu nadal się kręciła. Spowodowało to duży wyciek pamięci.

Na tylnej stronie kod był źle napisany, ale chodzi o to, że chcesz się upewnić, że rzeczy, które nie są używane, wykraczają poza zakres. Znalezienie tego za pomocą narzędzia do profilowania pamięci zajęło dużo czasu, ponieważ trudno jest wiedzieć, jakie rzeczy w pamięci są ważne i czego nie powinno tam być.

Piotr
źródło
2

Ja też muszę odpowiedzieć. JIT generuje tabele wraz z kodem na podstawie statycznej analizy wykorzystania zmiennych. Te wpisy tabeli są „GC-Roots” w bieżącej ramce stosu. W miarę przesuwania się wskaźnika instrukcji wpisy w tabeli stają się niepoprawne i gotowe do odśmiecania. Dlatego: Jeśli jest to zmienna o zasięgu, nie musisz ustawiać jej na null - GC zbierze obiekt. Jeśli jest to element członkowski lub zmienna statyczna, musisz ustawić wartość null

Hui
źródło