Müssen Sie Objekte entsorgen und sie auf null setzen, oder wird der Garbage Collector sie aufräumen, wenn sie aus dem Anwendungsbereich gehen?
Antworten
Zu viele Anzeigen?Objekte werden aufgeräumt, wenn sie nicht mehr verwendet werden und wenn der Garbage Collector es für richtig hält. Manchmal kann es notwendig sein, ein Objekt auf null
um es aus dem Geltungsbereich zu nehmen (z. B. ein statisches Feld, dessen Wert Sie nicht mehr benötigen), aber insgesamt ist es normalerweise nicht notwendig, den Wert auf null
.
Was die Entsorgung von Objekten betrifft, stimme ich mit @Andre überein. Wenn das Objekt IDisposable
es ist eine gute Idee, es zu entsorgen wenn Sie es nicht mehr benötigen, insbesondere wenn das Objekt nicht verwaltete Ressourcen verwendet. Wenn Sie nicht verwaltete Ressourcen nicht entsorgen, wird zu Speicherlecks führen .
Sie können die using
Anweisung, um ein Objekt automatisch zu entsorgen, sobald Ihr Programm den Geltungsbereich der using
Erklärung.
using (MyIDisposableObject obj = new MyIDisposableObject())
{
// use the object here
} // the object is disposed here
Was funktionell gleichbedeutend ist mit:
MyIDisposableObject obj;
try
{
obj = new MyIDisposableObject();
}
finally
{
if (obj != null)
{
((IDisposable)obj).Dispose();
}
}
Im Gegensatz zu C++ verlassen Objekte in C# nie den Anwendungsbereich. Sie werden vom Garbage Collector automatisch behandelt, wenn sie nicht mehr verwendet werden. Dies ist ein komplizierterer Ansatz als in C++, wo der Geltungsbereich einer Variablen völlig deterministisch ist. Der CLR-Garbage-Collector geht aktiv alle erstellten Objekte durch und stellt fest, ob sie verwendet werden.
Ein Objekt kann in einer Funktion "out of scope" gehen, aber wenn sein Wert zurückgegeben wird, dann würde GC prüfen, ob die aufrufende Funktion den Rückgabewert behält oder nicht.
Setzen von Objektreferenzen auf null
ist unnötig, da die Garbage Collection herausfindet, welche Objekte von anderen Objekten referenziert werden.
In der Praxis muss man sich nicht um die Zerstörung kümmern, es funktioniert einfach und ist großartig :)
Dispose
muss für alle Objekte aufgerufen werden, die die IDisposable
wenn Sie die Arbeit mit ihnen beendet haben. Normalerweise würden Sie eine using
Block mit diesen Objekten wie folgt:
using (var ms = new MemoryStream()) {
//...
}
エディトリアル Zum variablen Umfang. Craig hat gefragt, ob der Geltungsbereich von Variablen Auswirkungen auf die Lebensdauer von Objekten hat. Um diesen Aspekt der CLR richtig zu erklären, muss ich ein paar Konzepte aus C++ und C# erläutern.
Tatsächlicher Umfang der Variablen
In beiden Sprachen kann die Variable nur in dem Bereich verwendet werden, in dem sie definiert wurde - Klasse, Funktion oder ein Anweisungsblock, der von geschweiften Klammern umgeben ist. Der feine Unterschied ist jedoch, 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# erhalten Sie jedoch einen Compilerfehler:
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
}
Das macht Sinn, wenn man sich die generierte MSIL ansieht - alle von der Funktion verwendeten Variablen werden am Anfang der Funktion definiert. Werfen Sie einen Blick auf diese Funktion:
public static void Scope() {
int iVal = 8;
if(iVal == 8) {
int iVal2 = 5;
}
}
Nachfolgend ist die generierte AWL dargestellt. Beachten Sie, dass iVal2, die innerhalb des if-Blocks definiert ist, tatsächlich auf Funktionsebene definiert ist. Effektiv bedeutet dies, dass C# nur Klassen- und Funktionsebene Umfang so weit wie Variable Lebensdauer betroffen ist.
.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++ Geltungsbereich und Objektlebensdauer
Wann immer eine auf dem Stack zugewiesene C++-Variable den Anwendungsbereich verlässt, wird sie zerstört. Denken Sie daran, dass Sie in C++ Objekte auf dem Stack oder auf dem Heap erstellen können. Wenn Sie sie auf dem Stapel erstellen, werden sie, sobald die Ausführung den Anwendungsbereich verlässt, vom Stapel genommen und zerstört.
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 ein Speicherleck handelt. Bei Stack-Variablen gibt es dieses Problem nicht.
C# Objekt-Lebensdauer
In der CLR sind Objekte (d. h. Referenztypen) immer auf dem verwalteten Heap erstellt. Dies wird durch die Syntax der Objekterstellung noch verstärkt. Betrachten Sie diesen Codeschnipsel.
MyClass stackObj;
In C++ würde dies eine Instanz auf MyClass
auf dem Stapel und rufen seinen Standardkonstruktor auf. In C# würde es einen Verweis auf die Klasse MyClass
die auf nichts hinweisen. Die einzige Möglichkeit, eine Instanz einer Klasse zu erzeugen, ist die Verwendung von new
Betreiber:
MyClass stackObj = new MyClass();
In gewisser Weise sind C#-Objekte ähnlich wie Objekte, die mit new
Syntax in C++ - sie werden auf dem Heap erstellt, aber im Gegensatz zu C++-Objekten werden sie von der Laufzeitumgebung verwaltet, so dass man sich nicht um ihre Zerstörung kümmern muss.
Da die Objekte immer auf dem Heap wird die Tatsache, dass Objektreferenzen (d.h. Zeiger) aus dem Geltungsbereich herausgehen, überflüssig. Bei der Entscheidung, ob ein Objekt gesammelt werden soll, spielen mehr Faktoren eine Rolle als nur das Vorhandensein von Verweisen auf das Objekt.
C# Objekt-Referenzen
Jon Skeet Objektreferenzen in Java vergleichen zu Schnurstücken, die an dem Ballon, dem Objekt, befestigt sind. Die gleiche Analogie gilt für C#-Objektreferenzen. Sie verweisen einfach auf eine Stelle im Heap, die das Objekt enthält. Daher hat das Setzen auf null keine unmittelbare Auswirkung auf die Lebensdauer des Objekts, der Ballon existiert weiter, bis der GC ihn "platzen" lässt.
Wenn man die Analogie mit dem Ballon fortsetzt, erscheint es logisch, dass der Ballon, sobald er keine Fäden mehr hat, zerstört werden kann. In der Tat ist dies genau die Art und Weise, wie referenzierte Objekte in nicht verwalteten Sprachen funktionieren. Allerdings funktioniert dieser Ansatz bei zirkulären Referenzen nicht sehr gut. Stellen Sie sich zwei Ballons vor, die durch eine Schnur miteinander verbunden sind, aber keiner der beiden Ballons hat eine Schnur zu etwas anderem. Nach einfachen Regeln der Referenzzählung existieren beide weiter, auch wenn die gesamte Ballongruppe "verwaist" ist.
.NET-Objekte sind wie Heliumballons unter einem Dach. Wenn sich das Dach öffnet (GC läuft), schweben die unbenutzten Ballons davon, auch wenn es Gruppen von Ballons gibt, die aneinander gebunden sind.
.NET GC verwendet eine Kombination aus Generational GC und Mark and Sweep. Beim generationalen Ansatz untersucht die Laufzeit bevorzugt Objekte, die erst kürzlich zugewiesen wurden, da sie mit größerer Wahrscheinlichkeit ungenutzt sind. Beim mark and sweep-Ansatz geht die Laufzeit den gesamten Objektgraph durch und ermittelt, ob es Objektgruppen gibt, die ungenutzt sind. Auf diese Weise wird das Problem der zirkulären Abhängigkeiten angemessen gelöst.
Außerdem läuft .NET GC auf einem anderen Thread (dem so genannten Finalizer-Thread), da er eine Menge zu tun hat und die Ausführung auf dem Hauptthread Ihr Programm unterbrechen würde.
Wie andere bereits gesagt haben, sollten Sie auf jeden Fall anrufen Dispose
wenn die Klasse implementiert IDisposable
. Ich vertrete in dieser Frage einen ziemlich starren Standpunkt. Manche mögen behaupten, dass die Bezeichnung Dispose
auf DataSet
zum Beispiel, ist sinnlos, weil sie es zerlegt haben und gesehen haben, dass es nichts Sinnvolles bewirkt hat. Aber ich denke, dieses Argument ist nicht ganz richtig.
Lesen Sie este für eine interessante Debatte mit angesehenen Persönlichkeiten zu diesem Thema. Dann lesen Sie meine Argumentation aquí warum Jeffery Richter meiner Meinung nach im falschen Lager ist.
Nun zu der Frage, ob Sie einen Verweis auf 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");
}
Was glauben Sie also, wann das Objekt, auf das sich die a
für die Sammlung in Frage kommt? Wenn Sie nach dem Anruf an a = null
dann liegen Sie falsch. Wenn Sie sagten, nach dem Main
Methode abgeschlossen ist, dann sind Sie auch falsch. Die richtige Antwort ist, dass sie irgendwann für die Sammlung in Frage kommt während die Aufforderung an DoSomething
. Das ist richtig. Es ist förderfähig vor wird die Referenz auf null
und vielleicht sogar vor der Aufforderung zur DoSomething
komplettiert. Das liegt daran, dass der JIT-Compiler erkennen kann, wann Objektreferenzen nicht mehr dereferenziert werden, auch wenn sie noch verwurzelt sind.
Wenn das Objekt implementiert IDisposable
dann ja, Sie sollten sie entsorgen. Das Objekt könnte an nativen Ressourcen hängen (Dateihandles, Betriebssystemobjekte), die andernfalls nicht sofort freigegeben werden könnten. Dies kann zu Ressourcenmangel, Dateisperrproblemen und anderen subtilen Fehlern führen, die ansonsten vermieden werden könnten.
Siehe auch Implementieren einer Dispose-Methode auf MSDN.
- See previous answers
- Weitere Antworten anzeigen