Sie werden hier gestolpert und ziehen sehr falsche Schlussfolgerungen, weil Sie einen Debugger verwenden. Sie müssen Ihren Code so ausführen, wie er auf dem Computer Ihres Benutzers ausgeführt wird. Wechseln Sie zuerst mit Build + Configuration Manager zum Release-Build, und ändern Sie die Kombination "Aktive Lösungskonfiguration" in der oberen linken Ecke in "Release". Gehen Sie als Nächstes zu Extras + Optionen, Debuggen, Allgemein und deaktivieren Sie die Option "JIT-Optimierung unterdrücken".
Führen Sie nun Ihr Programm erneut aus und basteln Sie am Quellcode. Beachten Sie, dass die zusätzlichen Zahnspangen überhaupt keine Wirkung haben. Beachten Sie auch, dass das Festlegen der Variablen auf Null überhaupt keinen Unterschied macht. Es wird immer "1" gedruckt. Es funktioniert jetzt so, wie Sie es sich erhofft und erwartet haben.
Damit bleibt die Aufgabe zu erklären, warum es beim Ausführen des Debug-Builds so anders funktioniert. Dazu muss erklärt werden, wie der Garbage Collector lokale Variablen erkennt und wie sich dies auf das Vorhandensein eines Debuggers auswirkt.
Zunächst führt der Jitter zwei wichtige Aufgaben aus, wenn er die IL für eine Methode in Maschinencode kompiliert. Der erste ist im Debugger sehr gut sichtbar. Sie können den Maschinencode im Fenster Debug + Windows + Disassembly sehen. Die zweite Pflicht ist jedoch völlig unsichtbar. Außerdem wird eine Tabelle generiert, in der beschrieben wird, wie die lokalen Variablen im Methodenkörper verwendet werden. Diese Tabelle enthält einen Eintrag für jedes Methodenargument und jede lokale Variable mit zwei Adressen. Die Adresse, an der die Variable zuerst eine Objektreferenz speichert. Und die Adresse des Maschinencodebefehls, an dem diese Variable nicht mehr verwendet wird. Auch ob diese Variable im Stapelrahmen oder in einem CPU-Register gespeichert ist.
Diese Tabelle ist für den Garbage Collector von wesentlicher Bedeutung. Er muss wissen, wo bei der Durchführung einer Sammlung nach Objektreferenzen gesucht werden muss. Ziemlich einfach, wenn die Referenz Teil eines Objekts auf dem GC-Heap ist. Auf jeden Fall nicht einfach, wenn die Objektreferenz in einem CPU-Register gespeichert ist. Auf dem Tisch steht, wo man suchen muss.
Die "nicht mehr verwendete" Adresse in der Tabelle ist sehr wichtig. Dies macht den Müllsammler sehr effizient . Es kann eine Objektreferenz erfassen, auch wenn sie in einer Methode verwendet wird und die Ausführung dieser Methode noch nicht abgeschlossen ist. Was sehr häufig vorkommt, ist, dass Ihre Main () -Methode beispielsweise erst kurz vor dem Beenden Ihres Programms nicht mehr ausgeführt wird. Natürlich möchten Sie nicht, dass Objektreferenzen, die in dieser Main () -Methode verwendet werden, für die Dauer des Programms gültig sind, was einem Leck gleichkommt. Der Jitter kann anhand der Tabelle feststellen, dass eine solche lokale Variable nicht mehr nützlich ist, je nachdem, wie weit das Programm innerhalb dieser Main () -Methode vor dem Aufruf fortgeschritten ist.
Eine fast magische Methode, die mit dieser Tabelle zusammenhängt, ist GC.KeepAlive (). Es ist eine ganz besondere Methode, sie generiert überhaupt keinen Code. Die einzige Aufgabe besteht darin, diese Tabelle zu ändern. Es erstreckt sichdie Lebensdauer der lokalen Variablen, wodurch verhindert wird, dass die darin gespeicherte Referenz den Müll sammelt. Sie müssen es nur verwenden, um zu verhindern, dass der GC mit dem Sammeln einer Referenz zu eifrig wird. Dies kann in Interop-Szenarien auftreten, in denen eine Referenz an nicht verwalteten Code übergeben wird. Der Garbage Collector kann nicht sehen, dass solche Referenzen von einem solchen Code verwendet werden, da er nicht vom Jitter kompiliert wurde und daher nicht über die Tabelle verfügt, in der angegeben ist, wo nach der Referenz gesucht werden soll. Das Übergeben eines Delegatenobjekts an eine nicht verwaltete Funktion wie EnumWindows () ist das Beispiel dafür, wann Sie GC.KeepAlive () verwenden müssen.
Wie Sie Ihrem Beispiel-Snippet nach dem Ausführen im Release-Build entnehmen können , können lokale Variablen frühzeitig erfasst werden, bevor die Ausführung der Methode abgeschlossen ist. Noch stärker kann ein Objekt gesammelt bekommen , während eine seiner Methoden ausgeführt , wenn diese Methode nicht mehr bezieht sich dies . Es gibt ein Problem damit, es ist sehr umständlich, eine solche Methode zu debuggen. Da können Sie die Variable durchaus in das Überwachungsfenster einfügen oder überprüfen. Und es würde verschwinden, während Sie debuggen, wenn ein GC auftritt. Das wäre sehr unangenehm, daher ist sich der Jitter bewusst, dass ein Debugger angeschlossen ist. Es ändert sich danndie Tabelle und ändert die "zuletzt verwendete" Adresse. Und ändert es von seinem normalen Wert in die Adresse des letzten Befehls in der Methode. Dadurch bleibt die Variable am Leben, solange die Methode nicht zurückgegeben wurde. So können Sie es so lange beobachten, bis die Methode zurückkehrt.
Dies erklärt nun auch, was Sie zuvor gesehen haben und warum Sie die Frage gestellt haben. Es wird "0" ausgegeben, da der Aufruf von GC.Collect die Referenz nicht erfassen kann. Die Tabelle besagt, dass die Variable nach dem Aufruf von GC.Collect () bis zum Ende der Methode verwendet wird. Dies muss erzwungen werden, indem der Debugger angehängt und der Debug-Build ausgeführt wird.
Das Setzen der Variablen auf Null hat jetzt Auswirkungen, da der GC die Variable überprüft und keine Referenz mehr sieht. Aber stellen Sie sicher, dass Sie nicht in die Falle tappen, in die viele C # -Programmierer geraten sind. Das Schreiben dieses Codes war sinnlos. Es spielt keine Rolle, ob diese Anweisung vorhanden ist, wenn Sie den Code im Release-Build ausführen. In der Tat werden die Jitter - Optimierer entfernen diese Aussage , da sie keinerlei Auswirkungen haben. Schreiben Sie also keinen solchen Code, auch wenn dies Auswirkungen zu haben schien .
Ein letzter Hinweis zu diesem Thema: Dies bringt Programmierer in Schwierigkeiten, die kleine Programme schreiben, um etwas mit einer Office-App zu tun. Der Debugger bringt sie normalerweise auf den falschen Pfad. Sie möchten, dass das Office-Programm bei Bedarf beendet wird. Der geeignete Weg, dies zu tun, ist der Aufruf von GC.Collect (). Aber sie werden feststellen, dass es nicht funktioniert, wenn sie ihre App debuggen, und sie durch das Aufrufen von Marshal.ReleaseComObject () in ein Niemals-Niemals-Land führen. Manuelle Speicherverwaltung funktioniert selten ordnungsgemäß, da eine unsichtbare Schnittstellenreferenz leicht übersehen wird. GC.Collect () funktioniert tatsächlich, nur nicht, wenn Sie die App debuggen.