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 null
ist 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 :)
Dispose
muss für alle Objekte aufgerufen werden, die implementiert werden, IDisposable
wenn Sie mit ihnen fertig sind. Normalerweise würden Sie einen using
Block 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 MyClass
auf 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 new
Operators:
MyClass stackObj = new MyClass();
In gewisser Weise ähneln C # -Objekte Objekten, die mithilfe der new
Syntax 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.