Festlegen von Objekten auf Null / Nichts nach Verwendung in .NET


187

Sollten Sie alle Objekte auf null( Nothingin VB.NET) setzen, wenn Sie damit fertig sind?

Ich verstehe, dass es in .NET wichtig ist, alle Instanzen von Objekten zu entsorgen, die die IDisposableSchnittstelle implementieren , um einige Ressourcen freizugeben, obwohl das Objekt nach dem Entsorgen noch etwas sein kann (daher die isDisposedEigenschaft in Formularen), daher gehe ich davon aus, dass es sich noch befinden kann im Gedächtnis oder zumindest teilweise?

Ich weiß auch, dass ein Objekt, wenn es den Gültigkeitsbereich verlässt, zur Sammlung markiert wird und für den nächsten Durchgang des Garbage Collectors bereit ist (obwohl dies einige Zeit dauern kann).

In diesem Sinne wird es so eingestellt null, dass es das System beschleunigt, das den Speicher freigibt, da es nicht herausfinden muss, dass es nicht mehr im Umfang ist und es irgendwelche schlechten Nebenwirkungen gibt?

MSDN-Artikel tun dies niemals in Beispielen, und derzeit mache ich dies, da ich den Schaden nicht sehen kann. Ich bin jedoch auf eine Mischung von Meinungen gestoßen, sodass Kommentare nützlich sind.


4
+1 tolle Frage. Kennt jemand einen Umstand, unter dem der Compiler die Zuordnung insgesamt optimieren wird? dh jemand hat sich MSIL unter verschiedenen Umständen angesehen und IL notiert, um ein Objekt auf null zu setzen (oder das Fehlen davon).
Tim Medora

Antworten:


73

Karl ist absolut korrekt, es besteht keine Notwendigkeit, Objekte nach der Verwendung auf null zu setzen. Wenn ein Objekt implementiert wird IDisposable, stellen Sie einfach sicher, dass Sie aufrufen, IDisposable.Dispose()wenn Sie mit diesem Objekt fertig sind (eingeschlossen in einen try.. finallyoder einen using()Block). Aber selbst wenn Sie nicht daran denken, aufzurufen Dispose(), sollte die Finaliser-Methode für das Objekt Dispose()für Sie aufrufen .

Ich fand das eine gute Behandlung:

In IDisposable graben

und das

IDisposable verstehen

Es macht keinen Sinn, den GC und seine Managementstrategien zu erraten, da er sich selbst abstimmt und undurchsichtig ist. Es gab hier eine gute Diskussion über das Innenleben mit Jeffrey Richter auf Dot Net Rocks: Jeffrey Richter über das Windows-Speichermodell und Richters Buch CLR über C # Kapitel 20 hat eine großartige Behandlung:


6
Die Regel, nicht auf Null zu setzen, ist nicht "fest und schnell". Wenn das Objekt auf den großen Objekthaufen gelegt wird (Größe> 85 KB), hilft dies dem GC, wenn Sie das Objekt nach Abschluss auf Null setzen es benutzen.
Scott Dorman

Ich stimme in begrenztem Umfang zu, aber wenn Sie nicht anfangen, Speicherdruck zu spüren, sehe ich keine Notwendigkeit, vorzeitig zu optimieren, indem Objekte nach der Verwendung auf Null gesetzt werden.
Kev

21
Das ganze Geschäft mit "Nicht vorzeitig optimieren" klingt eher wie "Langsam bevorzugen und keine Sorge, weil die CPUs schneller werden und CRUD-Apps sowieso keine Geschwindigkeit benötigen." Es kann aber nur ich sein. :)
BobbyShaftoe

19
Was es wirklich bedeutet ist: "Der Garbage Collector kann den Speicher besser verwalten als Sie." Das könnte aber nur ich sein. :)
BobRodes

2
@BobbyShaftoe: Es ist wahrscheinlich genauso falsch zu sagen, "vorzeitige Optimierung ist immer schlecht", wie auf das entgegengesetzte Extrem von "klingt eher wie" lieber langsam "" zu springen. Kein vernünftiger Programmierer würde das auch sagen. Es geht um Nuancen und darum, klug zu sein, was Sie optimieren. Ich persönlich würde mir Sorgen um die Klarheit des Codes und die tatsächliche Testleistung machen, da ich persönlich gesehen habe, dass viele Leute (einschließlich meiner selbst, als ich jünger war) viel zu viel Zeit damit verbracht haben, den "perfekten" Algorithmus zu entwickeln, nur um 0,1 ms zu sparen in 100.000 Iterationen, während die Lesbarkeit vollständig aufgenommen wurde.
Brent Rittenhouse

36

Ein weiterer Grund, um zu vermeiden, dass Objekte auf Null gesetzt werden, wenn Sie damit fertig sind, besteht darin, dass sie tatsächlich länger am Leben bleiben können.

z.B

void foo()
{
    var someType = new SomeType();
    someType.DoSomething();
    // someType is now eligible for garbage collection         

    // ... rest of method not using 'someType' ...
}

ermöglicht es dem Objekt, auf das von someType verwiesen wird, nach dem Aufruf von "DoSomething" aber GC zu werden

void foo()
{
    var someType = new SomeType();
    someType.DoSomething();
    // someType is NOT eligible for garbage collection yet
    // because that variable is used at the end of the method         

    // ... rest of method not using 'someType' ...
    someType = null;
}

kann manchmal das Objekt bis zum Ende der Methode am Leben halten. Die JIT optimiert normalerweise die Zuweisung auf Null , sodass beide Codebits am Ende gleich sind.


Das ist ein interessanter Punkt. Ich habe immer gedacht, dass Objekte erst dann den Gültigkeitsbereich verlassen, wenn die Methode, in der sie den Gültigkeitsbereich haben, abgeschlossen ist. Es sei denn natürlich, das Objekt befindet sich in einem Using-Block oder ist explizit auf Nothing oder null gesetzt.
Guru Josh

1
Der bevorzugte Weg, um sicherzustellen, dass sie am Leben bleiben, ist die Verwendung von GC.KeepAlive(someType); Siehe ericlippert.com/2013/06/10/construction-destruction
NotMe


7

Ebenfalls:

using(SomeObject object = new SomeObject()) 
{
  // do stuff with the object
}
// the object will be disposed of

7

Im Allgemeinen müssen Objekte nach der Verwendung nicht auf Null gesetzt werden, aber in einigen Fällen ist dies eine gute Vorgehensweise.

Wenn ein Objekt IDisposable implementiert und in einem Feld gespeichert ist, ist es meiner Meinung nach gut, es auf Null zu setzen, nur um die Verwendung des entsorgten Objekts zu vermeiden. Die Fehler der folgenden Art können schmerzhaft sein:

this.myField.Dispose();
// ... at some later time
this.myField.DoSomething();

Es ist gut, das Feld nach dem Entsorgen auf Null zu setzen und einen NullPtrEx direkt an der Zeile zu erhalten, an der das Feld erneut verwendet wird. Andernfalls könnten Sie später auf einen kryptischen Fehler stoßen (abhängig davon, was DoSomething genau tut).


8
Nun, ein entsorgtes Objekt sollte ObjectDisposedException auslösen, wenn es bereits entsorgt wurde. Soweit ich weiß, erfordert dies überall Boilerplate-Code, aber andererseits ist Disposed ohnehin ein schlecht durchdachtes Paradigma.
nicodemus13

3
Strg + F für .Dispose(). Wenn Sie es finden, verwenden Sie IDisposable nicht richtig. Die einzige Verwendung für ein Einwegobjekt sollte in den Grenzen eines Verwendungsblocks liegen. Und nach dem using-Block haben Sie nicht einmal mehr Zugriff darauf myField. Und innerhalb des using-Blocks ist die Einstellung auf nullnicht erforderlich. Der using-Block entsorgt das Objekt für Sie.
Suamere

7

Es besteht die Möglichkeit, dass Ihr Code nicht eng genug strukturiert ist, wenn Sie das Bedürfnis nach nullVariablen haben.

Es gibt verschiedene Möglichkeiten, den Umfang einer Variablen einzuschränken:

Wie von Steve Tranby erwähnt

using(SomeObject object = new SomeObject()) 
{
  // do stuff with the object
}
// the object will be disposed of

Ebenso können Sie einfach geschweifte Klammern verwenden:

{
    // Declare the variable and use it
    SomeObject object = new SomeObject()
}
// The variable is no longer available

Ich finde, dass geschweifte Klammern ohne "Überschrift" verwendet werden, um den Code wirklich zu bereinigen und ihn verständlicher zu machen.


Ich habe einmal versucht, benutzerdefinierte lokale Bereiche zu verwenden (meistens als Smarta $$). Die Firma explodierte.
Suamere

Ein weiterer Hinweis: Dies liegt daran, dass der c # -Compiler Variablen mit lokalem Gültigkeitsbereich findet, die IDisposable implementieren, und .Dispose (meistens) aufruft, wenn ihr Gültigkeitsbereich endet. Allerdings ... SQL-Verbindungen sind eine große Zeit, in der .Dispose () niemals optimiert wird. Es gibt einige Typen, die explizite Aufmerksamkeit erfordern, deshalb mache ich persönlich immer explizite Dinge, nur damit ich nicht gebissen werde.
Suamere

5

Sie sollten eine Variable nur dann auf null setzen, wenn die Variable den Gültigkeitsbereich nicht verlässt und Sie die damit verbundenen Daten nicht mehr benötigen. Ansonsten besteht keine Notwendigkeit.


2
Das stimmt, aber es bedeutet auch, dass Sie Ihren Code wahrscheinlich umgestalten sollten. Ich glaube nicht, dass ich jemals eine Variable außerhalb des beabsichtigten Bereichs deklarieren musste.
Karl Seguin

2
Wenn unter "Variable" Objektfelder verstanden werden, ist diese Antwort sehr sinnvoll. In dem Fall, in dem "Variable" nur "lokale Variable" (einer Methode) bedeutet, handelt es sich hier wahrscheinlich um Nischenfälle (z. B. eine Methode, die viel länger als gewöhnlich ausgeführt wird).
stakx - nicht mehr beitragen

5

Im Allgemeinen muss nicht auf null gesetzt werden. Angenommen, Sie haben eine Reset-Funktion in Ihrer Klasse.

Dann können Sie dies tun, da Sie dispose nicht zweimal aufrufen möchten, da einige der Dispose möglicherweise nicht korrekt implementiert sind und die Ausnahme System.ObjectDisposed auslösen.

private void Reset()
{
    if(_dataset != null)
    {
       _dataset.Dispose();
       _dataset = null;
    }
    //..More such member variables like oracle connection etc. _oraConnection
 }

Am besten verfolgen Sie dies einfach mit einer separaten Flagge.
Thulani Chivandikwa

3

Diese Art von "Es ist nicht erforderlich, Objekte nach der Verwendung auf Null zu setzen" ist nicht ganz korrekt. Es gibt Zeiten, in denen Sie die Variable NULL machen müssen, nachdem Sie sie entsorgt haben.

Ja, Sie sollten IMMER anrufen .Dispose()oder .Close()auf etwas, das es hat, wenn Sie fertig sind. Sei es Dateihandles, Datenbankverbindungen oder Einwegobjekte.

Davon abgesehen ist das sehr praktische Muster von LazyLoad.

Sagen , ich habe und instanziiert ObjAvon class A. Class Ahat eine öffentliche Eigenschaft namens PropBvon class B.

PropBVerwendet intern die private Variable von _Bund ist standardmäßig null. Wenn PropB.Get()es verwendet wird, wird überprüft, ob _PropBes null ist, und wenn dies der Fall ist, werden die Ressourcen geöffnet, die zum Instanziieren von a Bin erforderlich sind _PropB. Es kehrt dann zurück _PropB.

Nach meiner Erfahrung ist dies ein wirklich nützlicher Trick.

Wenn Sie A auf eine Weise zurücksetzen oder ändern, bei der der Inhalt _PropBdas untergeordnete Element der vorherigen Werte von war A, müssen Sie Dispose AND null out zurücksetzen oder ändern , _PropBdamit LazyLoad zurückgesetzt werden kann, um den richtigen Wert für den Code abzurufen erfordert es.

Wenn Sie dies nur tun _PropB.Dispose()und kurz darauf erwarten, dass die Nullprüfung für LazyLoad erfolgreich ist, wird sie nicht null sein und Sie werden veraltete Daten anzeigen. In der Tat müssen Sie es nach null auf Null setzen, Dispose()nur um sicherzugehen.

Ich wünsche , dass es anders, aber ich habe jetzt bekomme Code , um dieses Verhaltens nach einer ausstellenden Dispose()auf einem _PropBund außerhalb der anrufenden Funktion, die das Entsorgen tat (und damit fast der Gültigkeitsbereich), die private prop ist noch nicht null, und die veralteten Daten sind immer noch da.

Letztendlich wird die veräußerte Eigenschaft auf Null gesetzt, aber das war aus meiner Sicht nicht deterministisch.

Der Hauptgrund, wie dbkk anspielt, ist, dass der übergeordnete Container ( ObjAmit PropB) die Instanz _PropBtrotz des Gültigkeitsbereichs behält Dispose().


Ein gutes Beispiel dafür, wie das manuelle Setzen auf Null einen schwerwiegenderen Fehler für den Anrufer bedeutet, was eine gute Sache ist.
rollt

1

In einigen Fällen ist es sinnvoll, Referenzen auf Null zu setzen. Wenn Sie beispielsweise eine Sammlung schreiben - wie eine Prioritätswarteschlange - und gemäß Ihrem Vertrag, sollten Sie diese Objekte für den Client nicht am Leben erhalten, nachdem der Client sie aus der Warteschlange entfernt hat.

Aber so etwas ist nur in langlebigen Sammlungen von Bedeutung. Wenn die Warteschlange das Ende der Funktion, in der sie erstellt wurde, nicht überlebt, ist dies viel weniger wichtig.

Im Großen und Ganzen sollten Sie sich wirklich nicht darum kümmern. Lassen Sie den Compiler und GC ihre Arbeit erledigen, damit Sie Ihre erledigen können.



1

Stephen Cleary erklärt in diesem Beitrag sehr gut: Soll ich Variablen auf Null setzen, um die Speicherbereinigung zu unterstützen?

Sagt:

Die kurze Antwort für den ungeduldigen Ja, wenn die Variable ein statisches Feld ist oder wenn Sie eine aufzählbare Methode (mit Yield Return) oder eine asynchrone Methode (mit Async und Warten) schreiben. Ansonsten nein.

Dies bedeutet, dass Sie in regulären Methoden (nicht aufzählbar und nicht asynchron) lokale Variablen, Methodenparameter oder Instanzfelder nicht auf null setzen.

(Auch wenn Sie IDisposable.Dispose implementieren, sollten Sie Variablen nicht auf null setzen.)

Das Wichtige, was wir berücksichtigen sollten, sind statische Felder .

Statische Felder sind immer Stammobjekte , daher werden sie vom Garbage Collector immer als "lebendig" betrachtet . Wenn ein statisches Feld auf ein Objekt verweist, das nicht mehr benötigt wird, sollte es auf null gesetzt werden, damit der Garbage Collector es als sammlungsfähig behandelt.

Das Setzen von statischen Feldern auf null ist bedeutungslos, wenn der gesamte Prozess heruntergefahren wird. Der gesamte Heap wird an diesem Punkt mit Müll gesammelt, einschließlich aller Stammobjekte.

Fazit:

Statische Felder ; das ist alles. Alles andere ist Zeitverschwendung .


0

Ich glaube, durch das Design der GC-Implementierer kann man GC nicht durch Nullifizierung beschleunigen . Ich bin sicher, sie würden es vorziehen, wenn Sie sich keine Gedanken darüber machen, wie / wann GC läuft - behandeln Sie es wie dieses allgegenwärtige Wesen, das Sie beschützt und auf Sie aufpasst ... (verbeugt sich mit gesenktem Kopf, hebt die Faust zum Himmel). .

Persönlich setze ich Variablen oft explizit auf null, wenn ich mit ihnen als Form der Selbstdokumentation fertig bin. Ich deklariere nicht, benutze es und setze es später auf null - ich nulle sofort, nachdem sie nicht mehr benötigt werden. Ich sage ausdrücklich: "Ich bin offiziell mit dir fertig ... sei weg ..."

Ist eine Aufhebung in einer GC-Sprache erforderlich? Ist es hilfreich für den GC? Vielleicht ja, vielleicht nein, weiß es nicht genau, von Natur aus kann ich es wirklich nicht kontrollieren, und unabhängig von der heutigen Antwort mit dieser oder jener Version könnten zukünftige GC-Implementierungen die Antwort außerhalb meiner Kontrolle ändern. Und wenn / wenn die Nullung optimiert ist, ist es kaum mehr als ein ausgefallener Kommentar, wenn Sie so wollen.

Ich denke , wenn es meine Absicht klarer auf die nächste arme Narr, der in meine Fußstapfen folgt macht, und wenn es „könnte“ potenziell manchmal Hilfe GC, dann lohnt es sich , es mir. Meistens fühle ich mich dadurch ordentlich und klar, und Mongo fühlt sich gerne ordentlich und klar. :) :)

Ich sehe das so: Es gibt Programmiersprachen, mit denen Leute anderen eine Vorstellung von der Absicht geben können und einem Compiler eine Jobanfrage, was zu tun ist - der Compiler konvertiert diese Anfrage in eine andere Sprache (manchmal mehrere) für eine CPU - Die CPU (s) können einen Hinweis darauf geben, welche Sprache Sie verwendet haben, welche Registerkarteneinstellungen, Kommentare, Stilschwerpunkte, Variablennamen usw. - bei einer CPU dreht sich alles um den Bitstrom, der angibt, welche Register und Opcodes und Speicherorte zu ändern sind. Viele in Code geschriebene Dinge werden nicht in der von uns angegebenen Reihenfolge in das konvertiert, was von der CPU verbraucht wird. Unser C, C ++, C #, Lisp, Babel, Assembler oder was auch immer eher Theorie als Realität ist, geschrieben als Arbeitserklärung. Was Sie sehen, ist nicht das, was Sie bekommen, ja, selbst in Assembler-Sprache.

Ich verstehe die Denkweise von "unnötigen Dingen" (wie Leerzeilen) "sind nichts als Rauschen und unübersichtlicher Code." Das war ich früher in meiner Karriere; Das verstehe ich total. An dieser Stelle neige ich zu dem, was den Code klarer macht. Es ist nicht so, dass ich meinen Programmen sogar 50 Zeilen "Rauschen" hinzufüge - es sind hier oder da ein paar Zeilen.

Es gibt Ausnahmen zu jeder Regel. In Szenarien mit flüchtigem Speicher, statischem Speicher, Rennbedingungen, Singletons, Verwendung "veralteter" Daten und all dieser Art von Fäulnis ist das anders: Sie MÜSSEN Ihren eigenen Speicher verwalten, als Apropos sperren und aufheben, da der Speicher nicht Teil von ist das GC'd Universum - hoffentlich versteht das jeder. Der Rest der Zeit mit GC-Sprachen ist eher eine Frage des Stils als der Notwendigkeit oder einer garantierten Leistungssteigerung.

Stellen Sie am Ende des Tages sicher, dass Sie verstehen, was für GC in Frage kommt und was nicht. entsprechend sperren, entsorgen und aufheben; Wachs drauf, Wachs runter; Einatmen Ausatmen; und für alles andere sage ich: Wenn es sich gut anfühlt, mach es. Ihr Kilometerstand kann variieren ... wie es sollte ...


0

Ich denke, etwas auf Null zurückzusetzen ist chaotisch. Stellen Sie sich ein Szenario vor, in dem das jetzt festgelegte Element beispielsweise über die Eigenschaft verfügbar gemacht wird. Wenn ein Teil des Codes diese Eigenschaft versehentlich verwendet, nachdem das Element entsorgt wurde, erhalten Sie eine Nullreferenzausnahme, die einige Untersuchungen erfordert, um genau herauszufinden, was vor sich geht.

Ich glaube, dass Framework-Einwegartikel das Auslösen von ObjectDisposedException ermöglichen, was aussagekräftiger ist. Wenn Sie diese nicht auf Null zurücksetzen, ist dies aus diesem Grund besser.


-1

Einige Objekte nehmen die .dispose()Methode an, mit der die Ressource aus dem Speicher entfernt wird.


11
Nein, tut es nicht; Dispose () funktioniert nicht collect das Objekt - es verwendet wird deterministisch sauber bis zu führen, in der Regel der Freigabe von nicht verwalteten Ressourcen.
Marc Gravell

1
In Anbetracht dessen, dass der Determinismus nur für die verwalteten Ressourcen gilt, nicht für die nicht verwalteten (dh den Speicher)
nicodemus13
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.