Müssen Sie Objekte entsorgen und auf null setzen?

310

Müssen Sie Objekte entsorgen und auf null setzen, oder bereinigt der Garbage Collector sie, wenn sie den Gültigkeitsbereich verlassen?

CJ7
quelle
4
Es scheint ein Konsens zu bestehen, dass Sie das Objekt nicht auf null setzen müssen, aber müssen Sie Dispose () ausführen?
CJ7
3
wie Jeff sagte: Kodierunghorror.com/blog/2009/01/…
Tanathos
9
Mein Rat ist immer zu entsorgen, wenn ein Objekt IDisposable implementiert. Verwenden Sie jedes Mal einen using-Block. Machen Sie keine Annahmen, überlassen Sie es nicht dem Zufall. Sie müssen die Dinge jedoch nicht auf null setzen. Ein Objekt hat gerade den Rahmen verlassen.
Peter
11
@ Peter: Verwenden Sie keine "using" -Blöcke
nlawalker
9
JEDOCH KÖNNEN SIE in Ihrer Dispose()Methode einige Verweise auf null setzen ! Dies ist eine subtile Variation dieser Frage, aber wichtig, da das zu entsorgende Objekt nicht wissen kann, ob es "außerhalb des Gültigkeitsbereichs" liegt (Aufruf Dispose()ist keine Garantie). Mehr hier: stackoverflow.com/questions/6757048/…
Kevin P. Rice

Antworten:

239

Objekte werden bereinigt, wenn sie nicht mehr verwendet werden und wenn der Garbage Collector dies für richtig hält. Manchmal müssen Sie möglicherweise ein Objekt festlegen null, um den Gültigkeitsbereich zu verlassen (z. B. ein statisches Feld, dessen Wert Sie nicht mehr benötigen), aber insgesamt muss es normalerweise nicht festgelegt werden null.

In Bezug auf die Entsorgung von Gegenständen stimme ich @Andre zu. Wenn das Objekt IDisposableist es eine gute Idee, es zu entsorgen , wenn Sie nicht mehr benötigen, vor allem , wenn das Objekt nicht verwalteten Ressourcen verwendet. Wenn nicht verwaltete Ressourcen nicht entsorgt werden, kommt es zu Speicherverlusten .

Mit der usingAnweisung können Sie ein Objekt automatisch entsorgen, sobald Ihr Programm den Gültigkeitsbereich der usingAnweisung verlässt .

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

Welches ist funktional äquivalent zu:

MyIDisposableObject obj;
try
{
    obj = new MyIDisposableObject();
}
finally
{
    if (obj != null)
    {
        ((IDisposable)obj).Dispose();
    }
}
Zach Johnson
quelle
4
Wenn obj ein Referenztyp ist, entspricht der finally-Block:if (obj != null) ((IDisposable)obj).Dispose();
Randy unterstützt Monica
1
@ Tuzo: Danke! Bearbeitet, um das widerzuspiegeln.
Zach Johnson
2
Eine Bemerkung zu IDisposable. Wenn ein Objekt nicht entsorgt wird, führt dies im Allgemeinen nicht zu einem Speicherverlust bei einer gut gestalteten Klasse. Wenn Sie mit nicht verwalteten Ressourcen in C # arbeiten, sollten Sie einen Finalizer haben, der die nicht verwalteten Ressourcen weiterhin freigibt. Dies bedeutet, dass anstelle der Freigabe der Ressourcen, wenn dies durchgeführt werden soll, der Zeitpunkt verschoben wird, zu dem der Garbage Collector das verwaltete Objekt fertigstellt. Es kann jedoch noch viele andere Probleme verursachen (z. B. nicht freigegebene Sperren). Sie sollten eine IDisposableobwohl entsorgen !
Aidiakapi
@RandyLevy Hast du eine Referenz dafür? Danke
Basic
Aber meine Frage ist, ob Dispose () irgendeine Logik implementieren muss? Muss es etwas tun? Oder intern, wenn Dispose () Signale GC genannt wird, die gut zu gehen sind? Ich habe zum Beispiel den Quellcode für den TextWriter überprüft und Dispose hat keine Implementierung.
Mihail Georgescu
137

Objekte verlassen in C # niemals den Gültigkeitsbereich wie in C ++. Sie werden vom Garbage Collector automatisch bearbeitet, wenn sie nicht mehr verwendet werden. Dies ist ein komplizierterer Ansatz als C ++, bei dem der Umfang einer Variablen vollständig deterministisch ist. Der CLR-Garbage Collector durchsucht aktiv alle erstellten Objekte und ermittelt, ob sie verwendet werden.

Ein Objekt kann in einer Funktion "außerhalb des Gültigkeitsbereichs" liegen. Wenn jedoch sein Wert zurückgegeben wird, prüft GC, ob die aufrufende Funktion den Rückgabewert beibehält oder nicht.

Das Festlegen von Objektreferenzen auf nullist nicht erforderlich, da die Speicherbereinigung funktioniert, indem ermittelt wird, auf welche Objekte von anderen Objekten verwiesen wird.

In der Praxis muss man sich keine Sorgen um Zerstörung machen, es funktioniert einfach und es ist großartig :)

Disposemuss für alle Objekte aufgerufen werden, die implementiert werden, IDisposablewenn Sie mit ihnen fertig sind. Normalerweise würden Sie einen usingBlock mit folgenden Objekten verwenden:

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

BEARBEITEN Auf variablen Bereich. Craig hat gefragt, ob der variable Bereich Auswirkungen auf die Objektlebensdauer hat. Um diesen Aspekt der CLR richtig zu erklären, muss ich einige Konzepte aus C ++ und C # erläutern.

Tatsächlicher variabler Umfang

In beiden Sprachen kann die Variable nur in demselben Bereich verwendet werden, in dem sie definiert wurde - Klasse, Funktion oder ein durch Klammern eingeschlossener Anweisungsblock. Der subtile Unterschied besteht jedoch darin, dass in C # Variablen in einem verschachtelten Block nicht neu definiert werden können.

In C ++ ist dies völlig legal:

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

In C # wird jedoch ein Compilerfehler angezeigt:

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
}

Dies ist sinnvoll, wenn Sie sich die generierte MSIL ansehen - alle von der Funktion verwendeten Variablen werden zu Beginn der Funktion definiert. Schauen Sie sich diese Funktion an:

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

Unten ist die generierte IL. Beachten Sie, dass iVal2, das im if-Block definiert ist, tatsächlich auf Funktionsebene definiert ist. Tatsächlich bedeutet dies, dass C # nur einen Bereich auf Klassen- und Funktionsebene hat, was die variable Lebensdauer betrifft.

.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 ++ - Bereich und Objektlebensdauer

Immer wenn eine auf dem Stapel zugewiesene C ++ - Variable den Gültigkeitsbereich verlässt, wird sie zerstört. Denken Sie daran, dass Sie in C ++ Objekte auf dem Stapel oder auf dem Heap erstellen können. Wenn Sie sie auf dem Stapel erstellen, werden sie vom Stapel gestapelt und zerstört, sobald die Ausführung den Bereich verlässt.

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

Wenn C ++ - Objekte auf dem Heap erstellt werden, müssen sie explizit zerstört werden, da es sich sonst um einen Speicherverlust handelt. Kein solches Problem mit Stapelvariablen.

C # Objektlebensdauer

In CLR werden Objekte (dh Referenztypen) immer auf dem verwalteten Heap erstellt. Dies wird durch die Objekterstellungssyntax weiter verstärkt. Betrachten Sie dieses Code-Snippet.

MyClass stackObj;

In C ++ würde dies eine Instanz MyClassauf dem Stapel erstellen und ihren Standardkonstruktor aufrufen. In C # würde ein Verweis auf eine Klasse erstellt MyClass, der auf nichts verweist. Die einzige Möglichkeit, eine Instanz einer Klasse zu erstellen, ist die Verwendung des newOperators:

MyClass stackObj = new MyClass();

In gewisser Weise ähneln C # -Objekte Objekten, die mithilfe der newSyntax in C ++ erstellt wurden. Sie werden auf dem Heap erstellt, werden jedoch im Gegensatz zu C ++ - Objekten von der Laufzeit verwaltet, sodass Sie sich nicht um deren Zerstörung kümmern müssen.

Da sich die Objekte immer auf dem Heap befinden, wird die Tatsache, dass Objektreferenzen (dh Zeiger) außerhalb des Gültigkeitsbereichs liegen, umstritten. Es gibt mehr Faktoren, die bestimmen, ob ein Objekt gesammelt werden soll, als nur das Vorhandensein von Verweisen auf das Objekt.

C # Objektreferenzen

Jon Skeet verglich Objektreferenzen in Java mit Zeichenfolgen, die an der Sprechblase angebracht sind, die das Objekt ist. Die gleiche Analogie gilt für C # -Objektreferenzen. Sie zeigen einfach auf eine Position des Heaps, der das Objekt enthält. Das Setzen auf Null hat also keine unmittelbare Auswirkung auf die Objektlebensdauer. Der Ballon bleibt bestehen, bis der GC ihn "knallt".

Wenn Sie die Ballonanalogie fortsetzen, erscheint es logisch, dass der Ballon zerstört werden kann, wenn er keine Schnüre mehr hat. Genau so funktionieren referenzgezählte Objekte in nicht verwalteten Sprachen. Nur dass dieser Ansatz für Zirkelverweise nicht sehr gut funktioniert. Stellen Sie sich zwei Ballons vor, die durch eine Schnur miteinander verbunden sind, aber keiner der Ballons hat eine Schnur zu irgendetwas anderem. Nach einfachen Ref-Count-Regeln existieren beide weiterhin, obwohl die gesamte Ballongruppe "verwaist" ist.

.NET-Objekte ähneln Heliumballons unter einem Dach. Wenn sich das Dach öffnet (GC läuft), schweben die nicht verwendeten Ballons weg, obwohl möglicherweise Gruppen von Ballons miteinander verbunden sind.

.NET GC verwendet eine Kombination aus Generations-GC und Mark and Sweep. Bei einem generativen Ansatz wird die Laufzeit bevorzugt, um Objekte zu untersuchen, die zuletzt zugewiesen wurden, da sie mit größerer Wahrscheinlichkeit nicht verwendet werden. Beim Markieren und Durchlaufen wird die Laufzeit durchlaufen, um das gesamte Objektdiagramm zu durchlaufen und herauszufinden, ob Objektgruppen nicht verwendet werden. Dies behandelt das Problem der zirkulären Abhängigkeit angemessen.

Außerdem wird .NET GC auf einem anderen Thread (dem sogenannten Finalizer-Thread) ausgeführt, da es ziemlich viel zu tun hat und dies auf dem Haupt-Thread Ihr Programm unterbrechen würde.

Igor Zevaka
quelle
1
@Igor: Mit "außerhalb des Gültigkeitsbereichs" meine ich, dass die Objektreferenz außerhalb des Kontexts liegt und im aktuellen Gültigkeitsbereich nicht referenziert werden kann. Sicherlich passiert dies immer noch in C #.
CJ7
@Craig Johnston, verwechseln Sie den vom Compiler verwendeten variablen Bereich nicht mit der variablen Lebensdauer, die durch die Laufzeit bestimmt wird - sie sind unterschiedlich. Eine lokale Variable ist möglicherweise nicht "live", obwohl sie sich noch im Gültigkeitsbereich befindet.
Randy unterstützt Monica
1
@Craig Johnston: Siehe blogs.msdn.com/b/ericgu/archive/2004/07/23/192842.aspx : "Es gibt keine Garantie dafür, dass eine lokale Variable bis zum Ende eines Bereichs aktiv bleibt, wenn dies nicht der Fall ist Es steht der Laufzeit frei, den vorhandenen Code zu analysieren und festzustellen, ob eine Variable über einen bestimmten Punkt hinaus nicht mehr verwendet wird, und diese Variable daher nicht über diesen Punkt hinaus aktiv zu halten (dh sie nicht als Root für die Zwecke zu behandeln) von GC).
Randy unterstützt Monica
1
@ Tuzo: Stimmt. Dafür ist GC.KeepAlive da.
Steven Sudit
1
@ Craig Johnston: Nein und ja. Nein, da die .NET-Laufzeit dies für Sie verwaltet und gute Arbeit leistet. Ja, weil der Programmierer nicht die Aufgabe hat, Code zu schreiben, der (nur) kompiliert wird, sondern Code, der ausgeführt wird . Manchmal ist es hilfreich zu wissen, was die Laufzeit unter der Decke tut (z. B. Fehlerbehebung). Man könnte argumentieren, dass es die Art von Wissen ist, die hilft, gute Programmierer von großartigen Programmierern zu trennen.
Randy unterstützt Monica
18

Wie andere gesagt haben, möchten Sie auf jeden Fall anrufen, Disposewenn die Klasse implementiert IDisposable. Ich nehme dazu eine ziemlich starre Position ein. Man könnte behaupten , dass Aufruf Disposeauf DataSet, zum Beispiel, ist sinnlos , weil sie es zerlegt und sehen , dass es nicht sinnvoll , nichts getan. Aber ich denke, dieses Argument enthält viele Irrtümer.

Lesen Sie dies für eine interessante Debatte von angesehenen Personen zu diesem Thema. Dann lesen Sie hier meine Argumentation , warum ich denke, dass Jeffery Richter im falschen Lager ist.

Nun zu der Frage, ob Sie einen Verweis auf festlegen sollen oder nicht null. Die Antwort ist nein. Lassen Sie mich meinen Standpunkt mit dem folgenden Code veranschaulichen.

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");
}

Wann kann das Objekt, auf das verwiesen awird, Ihrer Meinung nach gesammelt werden? Wenn Sie nach dem Anruf gesagt haben, a = nulldann liegen Sie falsch. Wenn Sie nach Abschluss der MainMethode gesagt haben, dass Sie auch falsch liegen. Die richtige Antwort ist, dass es irgendwann während des Anrufs bei zur Abholung berechtigt ist DoSomething. Das ist richtig. Es ist berechtigt, bevor die Referenz festgelegt wird nullund möglicherweise sogar bevor der Aufruf DoSomethingabgeschlossen ist. Dies liegt daran, dass der JIT-Compiler erkennen kann, wenn Objektreferenzen nicht mehr dereferenziert werden, selbst wenn sie noch verwurzelt sind.

Brian Gideon
quelle
3
Was ist, wenn asich ein privates Mitgliederfeld in einer Klasse befindet? Wenn anicht auf null gesetzt ist, kann der GC nicht wissen, ob aer in einer Methode erneut verwendet wird, oder? Somit awird nicht gesammelt, bis die gesamte enthaltende Klasse gesammelt ist. Nein?
Kevin P. Rice
4
@ Kevin: Richtig. Wenn aein Klassenmitglied wäre und die Klasse, die enthält, anoch verwurzelt und in Gebrauch wäre, würde es auch herumhängen. Dies ist ein Szenario, in dem die Einstellung von nullVorteil sein kann.
Brian Gideon
1
Ihr Punkt knüpft an einen Grund an, warum dies Disposewichtig ist: Es ist nicht möglich, Disposeein Objekt ohne einen verwurzelten Verweis darauf aufzurufen (oder eine andere nicht inlinierbare Methode). Wenn Sie Disposenach dem Aufrufen eines Objekts aufrufen, wird sichergestellt, dass während der gesamten Dauer der zuletzt ausgeführten Aktion weiterhin eine verwurzelte Referenz vorhanden ist. Das Verlassen aller Verweise auf ein Objekt ohne Aufruf Disposekann ironischerweise dazu führen, dass die Ressourcen des Objekts gelegentlich zu früh freigegeben werden .
Supercat
Dieses Beispiel und diese Erklärung scheinen nicht endgültig für den harten Vorschlag zu sein, niemals Verweise auf null zu setzen. Ich meine, abgesehen von Kevins Kommentar, scheint eine Referenz, die nach der Entsorgung auf null gesetzt wird , ziemlich harmlos zu sein . Was ist also der Schaden? Vermisse ich etwas
Dathompson
13

Sie müssen Objekte in C # niemals auf null setzen. Der Compiler und die Laufzeit kümmern sich darum, herauszufinden, wann sie nicht mehr im Umfang sind.

Ja, Sie sollten Objekte entsorgen, die IDisposable implementieren.

EMP
quelle
2
Wenn Sie einen langlebigen (oder sogar statischen) Verweis auf ein großes Objekt haben, müssen Sie ihn wantauf Null setzen, sobald Sie damit fertig sind, damit er frei zurückgefordert werden kann.
Steven Sudit
12
Wenn Sie jemals "damit fertig" sind, sollte es nicht statisch sein. Wenn es nicht statisch, sondern "langlebig" ist, sollte es bald, nachdem Sie damit fertig sind, immer noch außer Reichweite geraten. Die Notwendigkeit, Verweise auf null zu setzen, weist auf ein Problem mit der Struktur des Codes hin.
EMP
Sie können ein statisches Element haben, mit dem Sie fertig sind. Bedenken Sie: Eine statische Ressource, die in einem benutzerfreundlichen Format von der Festplatte gelesen und dann in ein für die Programmverwendung geeignetes Format analysiert wird. Sie können eine private Kopie der Rohdaten erhalten, die keinen weiteren Zweck erfüllt. (Beispiel aus der Praxis: Das Parsen ist eine Routine mit zwei
Durchgängen
1
Dann sollten keine Rohdaten in einem statischen Feld gespeichert werden, wenn sie nur vorübergehend verwendet werden. Sicher, Sie können das tun, es ist einfach aus genau diesem Grund keine gute Praxis: Sie müssen dann die Lebensdauer manuell verwalten.
EMP
2
Sie vermeiden dies, indem Sie die Rohdaten in einer lokalen Variablen in der Methode speichern, die sie verarbeitet. Die Methode gibt die verarbeiteten Daten zurück, die Sie behalten, aber die lokalen Daten für die Rohdaten werden beim Beenden der Methode nicht mehr gültig und automatisch gced.
EMP
11

Ich stimme der allgemeinen Antwort hier zu, dass Sie ja entsorgen sollten und nein, Sie sollten die Variable im Allgemeinen nicht auf null setzen ... aber ich wollte darauf hinweisen, dass es bei der Entsorgung NICHT in erster Linie um die Speicherverwaltung geht. Ja, es kann (und manchmal auch) bei der Speicherverwaltung helfen, aber sein Hauptzweck ist es, Ihnen eine deterministische Freigabe knapper Ressourcen zu ermöglichen.

Wenn Sie beispielsweise einen Hardware-Port (z. B. seriell), einen TCP / IP-Socket, eine Datei (im exklusiven Zugriffsmodus) oder sogar eine Datenbankverbindung öffnen, haben Sie jetzt verhindert, dass anderer Code diese Elemente verwendet, bis sie freigegeben werden. Dispose gibt diese Elemente im Allgemeinen frei (zusammen mit GDI und anderen "OS" -Handles usw., von denen 1000 verfügbar sind, aber insgesamt immer noch begrenzt sind). Wenn Sie dipose für das Eigentümerobjekt nicht aufrufen und diese Ressourcen explizit freigeben, versuchen Sie, dieselbe Ressource in Zukunft erneut zu öffnen (oder ein anderes Programm tut dies). Dieser Öffnungsversuch schlägt fehl, da für Ihr nicht verfügbares, nicht gesammeltes Objekt das Element noch geöffnet ist . Wenn der GC den Gegenstand sammelt (wenn das Entsorgungsmuster korrekt implementiert wurde), wird die Ressource natürlich freigegeben ... aber Sie wissen nicht, wann dies sein wird, also tun Sie es nicht. Ich weiß nicht, wann es sicher ist, diese Ressource erneut zu öffnen. Dies ist das Hauptproblem, das Dispose umgeht. Wenn Sie diese Handles freigeben, wird häufig auch Speicher freigegeben, und wenn Sie sie niemals freigeben, wird dieser Speicher möglicherweise nie freigegeben. Daher wird häufig über Speicherlecks oder Verzögerungen bei der Speicherbereinigung gesprochen.

Ich habe Beispiele aus der Praxis gesehen, die Probleme verursachen. Zum Beispiel habe ich ASP.Net-Webanwendungen gesehen, die möglicherweise keine Verbindung zur Datenbank herstellen können (wenn auch für kurze Zeit oder bis zum Neustart des Webserverprozesses), weil der Verbindungspool des SQL-Servers voll ist ... dh Es wurden so viele Verbindungen erstellt und nicht explizit in so kurzer Zeit freigegeben, dass keine neuen Verbindungen erstellt werden können und viele der Verbindungen im Pool, obwohl sie nicht aktiv sind, immer noch von nicht abgelegten und nicht gesammelten Objekten referenziert werden. nicht wiederverwendet werden. Wenn Sie die Datenbankverbindungen bei Bedarf ordnungsgemäß entsorgen, tritt dieses Problem nicht auf (zumindest nicht, es sei denn, Sie haben einen sehr hohen gleichzeitigen Zugriff).

Yort
quelle
11

Wenn das Objekt implementiert IDisposableist, sollten Sie es entsorgen. Das Objekt hängt möglicherweise an nativen Ressourcen (Dateihandles, Betriebssystemobjekte), die ansonsten möglicherweise nicht sofort freigegeben werden. Dies kann zu Ressourcenmangel, Problemen beim Sperren von Dateien und anderen subtilen Fehlern führen, die andernfalls vermieden werden könnten.

Siehe auch Implementieren einer Entsorgungsmethode in MSDN.

Chris Schmich
quelle
Aber ruft der Garabage-Sammler nicht Dispose () auf? Wenn ja, warum sollten Sie es anrufen müssen?
CJ7
Sofern Sie es nicht ausdrücklich anrufen, gibt es keine Garantie, Disposedie aufgerufen wird. Wenn Ihr Objekt an einer knappen Ressource festhält oder eine Ressource (z. B. eine Datei) sperrt, sollten Sie diese so schnell wie möglich freigeben. Das Warten auf den GC ist nicht optimal.
Chris Schmich
12
GC ruft niemals Dispose () auf. GC könnte einen Finalizer aufrufen, der gemäß Konvention Ressourcen bereinigen sollte.
Adrianm
@adrianm: Nicht mightanrufen, sondern willanrufen.
Leppie
2
@leppie: Finalizer sind nicht deterministisch und werden möglicherweise nicht aufgerufen (z. B. wenn die Appdomain entladen wird). Wenn Sie eine deterministische Finalisierung benötigen, müssen Sie das implementieren, was ich für einen kritischen Handler halte. Die CLR hat eine spezielle Behandlung dieser Objekte, um sicherzustellen, dass sie finalisiert sind (z. B. wird der Finalisierungscode vorab angepasst, um mit wenig Speicher
umzugehen
9

Wenn sie die IDisposable-Schnittstelle implementieren, sollten Sie sie entsorgen. Der Müllsammler kümmert sich um den Rest.

BEARBEITEN: Verwenden Sie am besten den usingBefehl, wenn Sie mit Einwegartikeln arbeiten:

using(var con = new SqlConnection("..")){ ...
Andre
quelle
5

Wenn ein Objekt implementiert wird IDisposable, sollten Sie aufrufen Dispose(oder Closein einigen Fällen wird Dispose für Sie aufgerufen).

Normalerweise müssen Sie keine Objekte festlegen null, da der GC weiß, dass ein Objekt nicht mehr verwendet wird.

Es gibt eine Ausnahme, wenn ich Objekte auf setze null. Wenn ich viele Objekte (aus der Datenbank) abrufe, an denen ich arbeiten muss, und sie in einer Sammlung (oder einem Array) speichere. Wenn die "Arbeit" erledigt ist, setze ich das Objekt auf null, weil der GC nicht weiß, dass ich mit der Arbeit fertig bin.

Beispiel:

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
quelle
4

Normalerweise müssen Felder nicht auf null gesetzt werden. Ich würde jedoch immer empfehlen, nicht verwaltete Ressourcen zu entsorgen.

Aus Erfahrung würde ich Ihnen auch raten, Folgendes zu tun:

  • Melden Sie sich von Ereignissen ab, wenn Sie sie nicht mehr benötigen.
  • Setzen Sie ein Feld mit einem Delegaten oder einem Ausdruck auf null, wenn es nicht mehr benötigt wird.

Ich bin auf einige sehr schwer zu findende Probleme gestoßen, die das direkte Ergebnis der Nichtbefolgung der obigen Ratschläge waren.

Ein guter Ort, um dies zu tun, ist Dispose (), aber früher ist normalerweise besser.

Wenn ein Verweis auf ein Objekt vorhanden ist, kann der Garbage Collector (GC) im Allgemeinen einige Generationen länger dauern, um festzustellen, dass ein Objekt nicht mehr verwendet wird. Währenddessen bleibt das Objekt im Speicher.

Dies ist möglicherweise kein Problem, bis Sie feststellen, dass Ihre App viel mehr Speicher belegt als erwartet. Schließen Sie in diesem Fall einen Speicherprofiler an, um festzustellen, welche Objekte nicht bereinigt werden. Das Festlegen von Feldern, die andere Objekte auf Null verweisen, und das Löschen von Sammlungen bei der Entsorgung können dem GC wirklich dabei helfen, herauszufinden, welche Objekte er aus dem Speicher entfernen kann. Der GC stellt den verwendeten Speicher schneller wieder her, wodurch Ihre App weniger speicherhungrig und schneller wird.

Marnix van Valen
quelle
1
Was meinst du mit "Veranstaltungen und Delegierten" - was sollte mit diesen "aufgeräumt" werden?
CJ7
@Craig - Ich habe meine Antwort bearbeitet. Hoffentlich klärt das ein bisschen.
Marnix van Valen
3

Rufen Sie immer dispose an. Das Risiko ist es nicht wert. Große verwaltete Unternehmensanwendungen sollten mit Respekt behandelt werden. Es können keine Annahmen getroffen werden, sonst wird es zurückkommen, um Sie zu beißen.

Hör nicht auf Leppie.

Viele Objekte implementieren IDisposable nicht, sodass Sie sich keine Sorgen machen müssen. Wenn sie wirklich außerhalb des Geltungsbereichs liegen, werden sie automatisch freigegeben. Außerdem bin ich nie auf die Situation gestoßen, in der ich etwas auf Null setzen musste.

Eine Sache, die passieren kann, ist, dass viele Objekte offen gehalten werden können. Dies kann die Speichernutzung Ihrer Anwendung erheblich erhöhen. Manchmal ist es schwierig herauszufinden, ob dies tatsächlich ein Speicherverlust ist oder ob Ihre Anwendung nur eine Menge Dinge tut.

Speicherprofil-Tools können bei solchen Dingen helfen, aber es kann schwierig sein.

Außerdem können Sie Ereignisse, die nicht benötigt werden, immer abbestellen. Seien Sie auch vorsichtig mit der WPF-Bindung und den Kontrollen. Keine übliche Situation, aber ich stieß auf eine Situation, in der ich ein WPF-Steuerelement hatte, das an ein zugrunde liegendes Objekt gebunden war. Das zugrunde liegende Objekt war groß und beanspruchte viel Speicher. Das WPF-Steuerelement wurde durch eine neue Instanz ersetzt, und die alte Instanz hing aus irgendeinem Grund immer noch herum. Dies verursachte einen großen Speicherverlust.

Im Nachhinein war der Code schlecht geschrieben, aber der Punkt ist, dass Sie sicherstellen möchten, dass Dinge, die nicht verwendet werden, außerhalb des Geltungsbereichs liegen. Es hat lange gedauert, diesen mit einem Speicherprofiler zu finden, da es schwierig ist zu wissen, welche Inhalte im Speicher gültig sind und welche nicht vorhanden sein sollten.

Peter
quelle
2

Ich muss auch antworten. Die JIT generiert Tabellen zusammen mit dem Code aus ihrer statischen Analyse der Variablennutzung. Diese Tabelleneinträge sind die "GC-Roots" im aktuellen Stapelrahmen. Mit fortschreitendem Befehlszeiger werden diese Tabelleneinträge ungültig und sind somit für die Speicherbereinigung bereit. Deshalb: Wenn es sich um eine Gültigkeitsbereichsvariable handelt, müssen Sie sie nicht auf null setzen - der GC sammelt das Objekt. Wenn es sich um ein Mitglied oder eine statische Variable handelt, müssen Sie sie auf null setzen

Hui
quelle