Czy musisz usuwać obiekty i ustawiać je na null?

Czy trzeba pozbywać się obiektów i ustawiać je na null, czy też garbage collector wyczyści je, gdy wyjdą poza zasięg?

Author: FishBasketGordo, 2010-05-28

11 answers

Obiekty zostaną wyczyszczone, gdy nie są już używane i gdy Śmieciarz uzna to za stosowne. Czasami może być konieczne ustawienie obiektu na null, aby wyszedł poza zakres (np. pole statyczne, którego wartości już nie potrzebujesz), ale ogólnie rzecz biorąc, zazwyczaj nie ma potrzeby ustawiania na null.

Jeśli chodzi o usuwanie przedmiotów, zgadzam się z @Andre. Jeśli obiekt jest IDisposable, to dobrym pomysłem jest pozbycie się go, gdy nie jest już potrzebny, zwłaszcza jeśli obiekt używa zasoby niezarządzane. Nie dysponowanie niezarządzanymi zasobami doprowadzi do wycieków pamięci.

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

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

Który jest funkcjonalnie równoważny:

MyIDisposableObject obj;
try
{
    obj = new MyIDisposableObject();
}
finally
{
    if (obj != null)
    {
        ((IDisposable)obj).Dispose();
    }
}
 252
Author: Zach Johnson,
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-07-02 08:06:15

Obiekty nigdy nie wychodzą poza zakres w C# tak jak w C++. Są one rozpatrywane przez Garbage Collector automatycznie, gdy nie są już używane. Jest to bardziej skomplikowane podejście niż C++, gdzie zakres zmiennej jest całkowicie deterministyczny. CLR garbage collector aktywnie przegląda wszystkie obiekty, które zostały utworzone i sprawdza, czy są używane.

Obiekt może wyjść "poza zakres" w jednej funkcji, ale jeśli jego wartość zostanie zwrócona, to GC sprawdzi czy lub nie Funkcja wywołująca utrzymuje zwracaną wartość.

Ustawianie odniesień do obiektów na null jest zbędne, ponieważ funkcja garbage collection polega na sprawdzaniu, do których obiektów odwołują się inne obiekty.

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

Dispose musi być wywołane na wszystkich obiektach implementujących IDisposable po zakończeniu pracy z nimi. Normalnie można by użyć bloku using z takimi obiektami jak więc:

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

Edytuj zmienny zakres. Craig zapytał, czy zmienna zakres ma jakikolwiek wpływ na żywotność obiektu. Aby właściwie wyjaśnić ten aspekt CLR, będę musiał wyjaśnić kilka pojęć z C++ i C#.

Zakres zmiennej rzeczywistej

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 zamknięty klamrami. Subtelna różnica polega jednak na tym, że w C# zmienne nie mogą być ponownie zdefiniowany 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 C# jednak 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 spojrzymy 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ę wygenerowany IL. Zauważ, że iVal2, który jest zdefiniowany wewnątrz bloku if, jest faktycznie zdefiniowany na poziomie funkcji. Skutecznie oznacza to, że C# ma tylko zakres klasy i poziomu funkcji w zakresie żywotności zmiennej.

.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

C++ scope and object lifetime

Gdy zmienna C++, przydzielona na stosie, wychodzi poza zakres, zostaje zniszczona. Pamiętaj, że w C++ możesz tworzyć obiekty na stosie lub na stercie. Kiedy utworzysz je na stosie, gdy execution opuści zakres, zostaną zerwane ze stosu i zostaną zniszczone.

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

Gdy obiekty C++ są tworzone na stercie, muszą być wyraźnie zniszczony, w przeciwnym razie jest to wyciek pamięci. Nie ma jednak takiego problemu ze zmiennymi stosu.

C# Object Lifetime

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

MyClass stackObj;

W C++ spowoduje to utworzenie instancji na MyClass na stosie i wywołanie jej domyślnego konstruktora. W C# tworzyłoby odniesienie do klasy MyClass, która nie wskazuje na wszystko. Jedynym sposobem na utworzenie instancji klasy jest użycie operatora new:

MyClass stackObj = new MyClass();

W pewnym sensie obiekty C# są podobne do obiektów, które są tworzone przy użyciu składni new W C++ - są tworzone na stercie, ale w przeciwieństwie do obiektów C++, są zarządzane przez runtime, więc nie musisz się martwić o ich zniszczenie.

Ponieważ obiekty są zawsze na stercie, fakt, że odniesienia do obiektów (np. wskaźniki) wychodzą poza zakres staje się dyskusyjny. Jest więcej czynniki związane z określeniem, czy obiekt ma być gromadzony, niż po prostu obecność odniesień do obiektu.

C # odniesienia do obiektów

Jon Skeet porównał odniesienia do obiektów w Javie do kawałków łańcuchów, które są przymocowane do balonu, który jest obiektem. Ta sama analogia dotyczy odniesień do obiektów C#. Po prostu wskazują lokalizację sterty, która zawiera obiekt. Tak więc ustawienie go na null nie ma natychmiastowego wpływu na żywotność obiektu, balon nadal istnieje, dopóki GC" wyskakuje " to.

Kontynuując analogię do balonu, wydaje się logiczne, że gdy balon nie będzie miał żadnych sznurków, może zostać zniszczony. W rzeczywistości tak właśnie działają obiekty z liczeniem referencji w językach Nie zarządzanych. Tyle, że to podejście nie działa dobrze w przypadku odniesień okrągłych. Wyobraź sobie dwa balony, które są połączone ze sobą sznurkiem, ale żaden balon nie ma sznurka do niczego innego. Zgodnie z prostymi regułami liczenia refów, obie nadal istnieć, mimo że cała grupa balonów jest "osierocona".

Obiekty. NET są jak balony z helem pod dachem. Gdy dach się otwiera ( GC runs) - niewykorzystane balony unoszą się, nawet jeśli mogą istnieć grupy balonów, które są połączone ze sobą.

. NET GC używa kombinacji generacyjnych GC oraz mark i sweep. Podejście pokoleniowe polega na tym, że runtime preferuje sprawdzanie obiektów, które zostały przydzielone ostatnio, ponieważ są one bardziej narażone na być nieużywane, oznaczanie i zamiatanie polega na przeszukiwaniu całego wykresu obiektów i sprawdzaniu, czy istnieją grupy obiektów, które są nieużywane. To odpowiednio rozwiązuje problem zależności kołowych.

Ponadto. NET GC działa na innym wątku (tzw. wątku finalizera), ponieważ ma sporo do zrobienia, a zrobienie tego w głównym wątku przerwałoby Twój program.

 138
Author: Igor Zevaka,
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
2010-05-29 03:15:29

Jak mówili inni, zdecydowanie chcesz wywołać Dispose jeśli klasa implementuje IDisposable. Mam dość sztywne stanowisko w tej sprawie. Niektórzy mogą twierdzić, że wywołanie Dispose na DataSet, na przykład, jest bezcelowe, ponieważ zdemontowali go i zobaczyli, że nie zrobił nic znaczącego. Ale myślę, że są błędne w tym argumencie.

Przeczytaj to {[16] } dla interesującej debaty szanowanych osób na ten temat. To przeczytaj moje rozumowanie tutaj dlaczego myślę Jeffery Richter jest w złym obozie.

Teraz, czy należy ustawić odniesienie do null. Odpowiedź brzmi nie. Pozwolę sobie zilustrować mój punkt za pomocą poniższego 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 obiekt, do którego odnosi się a kwalifikuje się do kolekcji? Jeśli powiedziałeś po telefonie do a = null, to się mylisz. Jeśli powiedziałeś po zakończeniu metody Main, również się mylisz. Poprawna odpowiedź jest taka, że kwalifikuje się do zbierania kiedyś podczas zadzwoń do DoSomething. Zgadza się. Jest to kwalifikowane przed Referencja jest ustawiona na null, a być może nawet przed zakończeniem wywołania do DoSomething. Dzieje się tak dlatego, że kompilator JIT może rozpoznać, kiedy odwołania do obiektów nie są już dereferowane, nawet jeśli są nadal zakorzenione.

 18
Author: Brian Gideon,
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-23 11:47:25

Nigdy nie musisz ustawiać obiektów na null w C#. Kompilator i środowisko uruchomieniowe zajmą się ustaleniem, kiedy nie będą już w zasięgu.

Tak, należy pozbyć się obiektów, które implementują IDisposable.

 15
Author: EMP,
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
2010-05-28 06:30:08

Jeśli obiekt implementuje IDisposable, to tak, powinieneś go usunąć. Obiekt może być zawieszony na natywnych zasobach (uchwytach plików, obiektach systemu operacyjnego), które w przeciwnym razie 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ąć.

Patrz równieżimplementacja metody Dispose w MSDN.

 12
Author: Chris Schmich,
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
2010-06-01 23:56:02

Zgadzam się z powszechną odpowiedzią tutaj, że tak powinieneś się pozbyć i nie generalnie nie powinieneś ustawiać zmiennej NA null... chciałem jednak zaznaczyć, że dispose nie polega przede wszystkim na zarządzaniu pamięcią. Tak, może pomóc (i czasami ma) w zarządzaniu pamięcią, ale jego głównym celem jest zapewnienie deterministycznego uwalniania 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żliwiłeś innym kodom używanie tych elementów do czasu ich wydania. Dispose generalnie uwalnia te elementy (wraz z GDI i innymi uchwytami " os " itp. które są dostępne 1000, ale nadal są ograniczone ogólnie). Jeśli nie wywołasz dipose na obiekcie właściciela i jawnie zwolnisz te zasoby, spróbuj ponownie otworzyć ten sam zasób w przyszłości (lub zrobi to inny program), ta próba otwarcia nie powiedzie się, ponieważ twój niedysponowany, niezabezpieczony obiekt nadal ma otwarty przedmiot. Oczywiście, gdy GC zbierze element (jeśli wzór utylizacji został poprawnie zaimplementowany), zasób zostanie zwolniony... ale nie wiesz, kiedy to nastąpi, więc nie wiesz, kiedy można ponownie otworzyć ten zasób. Jest to podstawowa kwestia. Oczywiście, zwolnienie tych uchwytów często zwalnia również pamięć i nigdy ich nie uwolnienie może nigdy nie zwolnić tej pamięci... stąd cała rozmowa o wyciekach pamięci lub opóźnieniach w czyszczeniu pamięci w górę.

Widziałem prawdziwe przykłady tego powodowania problemów. Na przykład, widziałem ASP.Net aplikacje internetowe, które ostatecznie nie łączą się z bazą danych (chociaż przez krótki czas lub do czasu ponownego uruchomienia procesu serwera www), ponieważ pula połączeń serwera sql jest pełna... tzn. tak wiele połączeń zostało utworzonych i nie zostało wyraźnie uwolnionych w tak krótkim czasie, że nie można utworzyć nowych połączeń i wiele połączeń w Puli, chociaż nie aktywne, są nadal odwoływane przez obiekty niezidentyfikowane i nieodebrane, więc nie mogą być ponownie użyte. Poprawne usuwanie połączeń z bazą danych w razie potrzeby zapewnia, że ten problem nie wystąpi (przynajmniej nie, chyba że masz bardzo wysoki dostęp współbieżny).

 11
Author: Yort,
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
2010-06-01 23:21:12

Jeśli zaimplementują IDisposable interface, powinieneś je usunąć. Śmieciarz zajmie się resztą.

EDIT: najlepiej jest użyć polecenia using podczas pracy z przedmiotami jednorazowymi:

using(var con = new SqlConnection("..")){ ...
 10
Author: Andre,
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-09-26 18:15:14

Gdy obiekt implementuje IDisposable powinieneś wywołać Dispose (lub Close, w niektórych przypadkach, które wywołają Dispose dla Ciebie).

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

Jest jeden wyjątek, gdy ustawiam obiekty na null. Kiedy pobieram wiele obiektów (z bazy danych), na których muszę pracować, i przechowuję je w kolekcji (lub tablicy). Po wykonaniu "pracy" ustawiam obiekt na null, ponieważ GC nie wie, że skończyłem z tym 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
 5
Author: GvS,
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
2010-06-02 08:07:13

Normalnie nie ma potrzeby ustawiania pól na null. Zawsze polecam jednak pozbywanie się niezarządzanych zasobów.

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

  • Anuluj subskrypcję wydarzeń, Jeśli już ich nie potrzebujesz.
  • Ustaw dowolne pole zawierające delegata lub wyrażenie na null, jeśli nie jest już potrzebne.

Natknąłem się na kilka bardzo trudnych do znalezienia problemów, które były bezpośrednim wynikiem nieprzestrzegania powyższych porad.

Dobre miejsce aby to zrobić, należy użyć Dispose (), ale im szybciej, tym lepiej.

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

To może nie być problemem, dopóki nie okaże się, że aplikacja zuż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. Ustawianie pól odwoływanie się do innych obiektów do null i czyszczenie kolekcji znajdujących się w dyspozycji może naprawdę pomóc GC dowiedzieć się, jakie obiekty może usunąć z pamięci. GC odzyska zużytą pamięć szybciej, dzięki czemu aplikacja będzie o wiele mniej głodna i szybsza.

 4
Author: Marnix van Valen,
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
2010-05-28 15:17:56

Zawsze dzwoń. Nie warto ryzykować. Duże zarządzane aplikacje korporacyjne powinny być traktowane z szacunkiem. Nie można zakładać, bo inaczej wróci, by cię ugryźć.

Nie słuchaj leppiego.

Wiele obiektów nie implementuje IDisposable, więc nie musisz się o nie martwić. Jeśli rzeczywiście wyjdą poza zasięg, zostaną automatycznie uwolnieni. Również nigdy nie natknąłem się na sytuację, w której musiałem ustawić coś na null.

One rzecz, która może się zdarzyć, jest to, że wiele obiektów można trzymać otwarte. Może to znacznie zwiększyć zużycie pamięci aplikacji. Czasami trudno jest ustalić, czy jest to rzeczywiście wyciek pamięci, czy Twoja aplikacja po prostu robi wiele rzeczy.

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

Ponadto zawsze Anuluj subskrypcję zdarzeń, które nie są potrzebne. Należy również uważać na wiązanie i sterowanie WPF. Nie jest to zwykła sytuacja, ale ja natknąłem się na sytuację, w której miałem kontrolę WPF, która była związana z podstawowym obiektem. Podstawowy obiekt był duży i zajmował dużą ilość pamięci. Kontrola WPF została zastąpiona nową instancją, a stara wciąż wisiała w pobliżu z jakiegoś powodu. Spowodowało to duży wyciek pamięci.

W hindsite kod był źle napisany, ale chodzi o to, że chcesz się upewnić, że rzeczy, które nie są używane, wychodzą poza zakres. To zajęło dużo czasu, aby znaleźć z memory profiler, ponieważ trudno jest wiedzieć, co w pamięci jest ważne, a czego nie powinno tam być.

 4
Author: peter,
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
2010-06-02 00:53:24

Ja też muszę odpowiedzieć. JIT generuje tabele wraz z kodem na podstawie statycznej analizy użycia zmiennych. Te wpisy w tabeli są "Gc-Roots" w bieżącej ramce stosu. Wraz ze wzrostem wskaźnika instrukcji, te wpisy w tabeli stają się nieważne i gotowe do usunięcia śmieci. Dlatego: jeśli jest zmienną o zasięgu, nie trzeba jej ustawiać na null - GC pobierze obiekt. Jeśli jest członkiem lub zmienną statyczną, musisz ustawić ją na null

 2
Author: Hui,
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
2011-07-28 12:45:50