Impostazione degli oggetti su Null / Nothing dopo l'uso in .NET


187

Dovresti impostare tutti gli oggetti su null( Nothingin VB.NET) una volta che hai finito con loro?

Comprendo che in .NET è essenziale disporre di eventuali istanze di oggetti che implementano l' IDisposableinterfaccia per rilasciare alcune risorse sebbene l'oggetto possa essere ancora qualcosa dopo che è stato eliminato (quindi la isDisposedproprietà nei moduli), quindi presumo che possa ancora risiedere in memoria o almeno in parte?

So anche che quando un oggetto esce dal campo di applicazione, viene contrassegnato per la raccolta pronto per il passaggio successivo del Garbage Collector (anche se ciò potrebbe richiedere del tempo).

Quindi, con questo in mente, lo imposterà per nullaccelerare il sistema rilasciando la memoria in quanto non deve capire che non ha più portata e sono effetti collaterali negativi?

Gli articoli MSDN non lo fanno mai negli esempi e attualmente lo faccio perché non riesco a vedere il danno. Tuttavia mi sono imbattuto in un misto di opinioni, quindi eventuali commenti sono utili.


4
+1 ottima domanda. Qualcuno sa una circostanza in cui il compilatore ottimizzerà del tutto l'assegnazione? cioè qualcuno ha guardato MSIL in circostanze diverse e ha notato IL per aver impostato un oggetto su null (o la sua mancanza).
Tim Medora,

Risposte:


73

Karl ha assolutamente ragione, non è necessario impostare gli oggetti su null dopo l'uso. Se un oggetto implementa IDisposable, assicurati di chiamare IDisposable.Dispose()quando hai finito con quell'oggetto (racchiuso in un try.. finally, o, un using()blocco). Ma anche se non ricordi di chiamare Dispose(), il metodo finalizzatore sull'oggetto dovrebbe chiamarti Dispose().

Ho pensato che fosse un buon trattamento:

Scavando in IDisposable

e questo

Capire IDisposable

Non ha senso provare a indovinare il GC e le sue strategie di gestione perché è auto-tuning e opaco. C'è stata una buona discussione sul funzionamento interno con Jeffrey Richter su Dot Net Rocks qui: Jeffrey Richter sul modello di memoria di Windows e il libro Richters CLR via C # capitolo 20 ha un ottimo trattamento:


6
La regola di non impostare su null non è "difficile e veloce" ... se l'oggetto viene inserito nell'heap di oggetti di grandi dimensioni (la dimensione è> 85 KB) aiuterà il GC se si imposta l'oggetto su null al termine usandolo.
Scott Dorman,

Sono d'accordo in misura limitata, ma a meno che non inizi a sperimentare la pressione della memoria, non vedo la necessità di "ottimizzare prematuramente" impostando gli oggetti su null dopo l'uso.
Kev,

21
L'intera attività di "non ottimizzare prematuramente" suona più come "Preferisci lento e non preoccuparti perché le CPU stanno diventando più veloci e le app CRUD non hanno comunque bisogno di velocità". Potrei essere solo io però. :)
BobbyShaftoe il

19
Ciò che significa veramente è "Il Garbage Collector è più bravo a gestire la memoria di te." Potrei essere solo io però. :)
BobRodes

2
@BobbyShaftoe: Probabilmente è sbagliato dire "l'ottimizzazione prematura è sempre cattiva", come lo è saltare all'estremo opposto di "suoni più come 'preferisci lento'". Nemmeno un programmatore ragionevole direbbe. Si tratta di sfumature ed essere intelligenti su ciò che ottimizzi. Personalmente mi preoccuperei per la chiarezza del codice e POI EFFETTUALMENTE le prestazioni poiché ho visto personalmente molte persone (incluso me stesso quando ero più giovane) passare troppo tempo a creare l'algoritmo "perfetto", solo per risparmiare 0,1 ms in 100.000 iterazioni mentre la leggibilità è stata completamente girata.
Brent Rittenhouse,

36

Un altro motivo per evitare di impostare gli oggetti su null al termine è che possono effettivamente mantenerli in vita più a lungo.

per esempio

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

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

consentirà all'oggetto riferito da someType di essere GC'd dopo la chiamata a "DoSomething" ma

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

a volte può mantenere in vita l'oggetto fino alla fine del metodo. Il JIT di solito ottimizza l'assegnazione a null , quindi entrambi i bit di codice finiscono per essere gli stessi.


Questo è un punto interessante. Ho sempre pensato che gli oggetti non escano dal campo di applicazione fino a quando il metodo in cui sono definiti non è completo. A meno che, ovviamente, l'oggetto non sia all'interno di un blocco Using o sia esplicitamente impostato su Nothing o null.
Guru Josh,

1
Il modo preferito per assicurarsi che rimangano vivi è usare GC.KeepAlive(someType); Vedi ericlippert.com/2013/06/10/construction-destruction
NotMe

14

No non null oggetti. Puoi controllare http://codebetter.com/blogs/karlseguin/archive/2008/04/27/foundations-of-programming-pt-7-back-to-basics-memory.aspx per ulteriori informazioni, ma impostando le cose null non farà nulla, tranne sporcare il tuo codice.


1
Spiegazione dettagliata e dettagliata sulla memoria nel collegamento condiviso
user2323308

Collegamento interrotto. Senza contenuto collegato, questa risposta è piuttosto utilizzata e deve essere eliminata.
Imre Pühvel,

7

Anche:

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

7

In generale, non è necessario null oggetti dopo l'uso, ma in alcuni casi trovo che sia una buona pratica.

Se un oggetto implementa IDisposable ed è memorizzato in un campo, penso che sia buono annullarlo, solo per evitare di usare l'oggetto disposto. I bug del seguente tipo possono essere dolorosi:

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

È bene annullare il campo dopo averlo eliminato e ottenere un NullPtrEx proprio sulla riga in cui il campo viene riutilizzato. Altrimenti, potresti imbatterti in un bug criptico lungo la linea (a seconda di esattamente cosa fa DoSomething).


8
Bene, un oggetto disposto dovrebbe lanciare ObjectDisposedException se è già stato eliminato. Questo, per quanto ne so, richiede codice boilerplate ovunque, ma poi Disposed è comunque un paradigma mal pensato.
nicodemus13

3
Ctrl + F per .Dispose(). Se lo trovi, non stai usando IDisposable correttamente. L'unico uso per un oggetto usa e getta dovrebbe essere nei confini di un blocco di utilizzo. E dopo aver usato il blocco, non hai nemmeno più accesso a myField. E all'interno del blocco using, l'impostazione su nullnon è necessaria, il using-block disporrà l'oggetto per te.
Suamere,

7

È probabile che il tuo codice non sia sufficientemente strutturato se senti il ​​bisogno di nullvariabili.

Esistono diversi modi per limitare l'ambito di una variabile:

Come menzionato da Steve Tranby

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

Allo stesso modo, puoi semplicemente usare le parentesi graffe:

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

Trovo che usare parentesi graffe senza "intestazione" per ripulire il codice e renderlo più comprensibile.


Ho provato a utilizzare ambiti locali personalizzati una volta (principalmente essendo un smarta $$). La compagnia esplose.
Suamere,

In un'altra nota: questo perché il compilatore c # troverà variabili con ambito locale che implementano IDisposable e chiamerà .Dispose (la maggior parte delle volte) quando termina il loro ambito. Tuttavia ... SQL Connections è un grande momento in cui .Dispose () non è mai ottimizzato. Ci sono alcuni tipi che richiedono un'attenzione esplicita, quindi personalmente faccio sempre le cose esplicitamente solo per non essere morso.
Suamere,

5

L'unica volta in cui è necessario impostare una variabile su null è quando la variabile non esce dall'ambito e non sono più necessari i dati ad essa associati. Altrimenti non è necessario.


2
È vero, ma significa anche che dovresti probabilmente refactoring il tuo codice. Non credo di aver mai avuto bisogno di dichiarare una variabile al di fuori del suo scopo previsto.
Karl Seguin,

2
Se si intende che "variabile" include campi oggetto, questa risposta ha molto senso. Nel caso in cui "variabile" significhi solo "variabile locale" (di un metodo), probabilmente stiamo parlando di casi di nicchia qui (ad esempio un metodo che funziona per un arco di tempo molto più lungo del solito).
stakx - non contribuisce più

5

In generale, non è necessario impostare su null. Supponiamo di avere una funzionalità di ripristino nella tua classe.

Quindi potresti farlo, perché non vuoi chiamare due volte Dispose, poiché parte di Dispose potrebbe non essere implementata correttamente e generare un'eccezione System.ObjectDisposed.

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

Forse è meglio rintracciarlo con una bandiera separata.
Thulani Chivandikwa,

3

questo tipo di "non è necessario impostare oggetti su null dopo l'uso" non è del tutto esatto. A volte è necessario NULL della variabile dopo averla eliminata.

Sì, dovresti SEMPRE chiamare .Dispose()o .Close()su qualsiasi cosa abbia quando hai finito. Che si tratti di handle di file, connessioni al database o oggetti usa e getta.

Separato da quello è il modello molto pratico di LazyLoad.

Dire che ho e un'istanza ObjAdi class A. Class Aha una proprietà pubblica chiamata PropBdi class B.

Internamente, PropButilizza la variabile privata di _Be di default su null. Quando PropB.Get()viene utilizzato, verifica se _PropBè null e, in caso affermativo, apre le risorse necessarie per Bcreare un'istanza in _PropB. Quindi ritorna _PropB.

Per la mia esperienza, questo è un trucco davvero utile.

Il punto in cui arriva la necessità di null è se si reimposta o si modifica A in qualche modo che il contenuto di _PropBera figlio dei valori precedenti di A, sarà necessario _PropBdisporre e annullare l'output in modo che LazyLoad possa reimpostare per recuperare il valore corretto SE il codice lo richiede.

Se lo fai solo _PropB.Dispose()e poco dopo ti aspetti che il controllo null di LazyLoad abbia esito positivo, non sarà nullo e vedrai i dati non aggiornati. In effetti, devi annullarlo dopo Dispose()solo per essere sicuro.

Sono sicuro che lo fosse altrimenti, ma ho il codice in questo momento esibendo questo comportamento dopo una Dispose()su un _PropBe al di fuori della funzione di chiamata che ha fatto il Dispose (e quindi quasi fuori portata), l'elica privato non è ancora nulla, e i dati non aggiornati sono ancora lì.

Alla fine, la proprietà disposta verrà annullata, ma questo è stato non deterministico dal mio punto di vista.

Il motivo principale, come allude dbkk, è che il contenitore padre ( ObjAcon PropB) mantiene l'istanza di _PropBnell'ambito, nonostante il Dispose().


Un buon esempio che mostra come l'impostazione di null manualmente significhi un errore più fatale per il chiamante, il che è positivo.
lancia il

1

Ci sono alcuni casi in cui ha senso fare riferimento a null. Ad esempio, quando stai scrivendo una raccolta, come una coda prioritaria, e in base al tuo contratto, non dovresti tenere in vita quegli oggetti per il client dopo che il client li ha rimossi dalla coda.

Ma questo genere di cose conta solo nelle collezioni di lunga durata. Se la coda non sopravviverà alla fine della funzione in cui è stata creata, allora conta molto meno.

Nel complesso, non dovresti davvero preoccuparti. Lascia che compilatore e GC facciano il loro lavoro in modo che tu possa fare il tuo.


1

Dai un'occhiata anche a questo articolo: http://www.codeproject.com/KB/cs/idisposable.aspx

Per la maggior parte, l'impostazione di un oggetto su null non ha alcun effetto. L'unica volta che dovresti essere sicuro di farlo è se stai lavorando con un "oggetto di grandi dimensioni", che ha una dimensione maggiore di 84 KB (come le bitmap).


1

Stephen Cleary spiega molto bene in questo post: dovrei impostare le variabili su null per aiutare la raccolta dei rifiuti?

dice:

La risposta breve, per l'impaziente Sì, se la variabile è un campo statico o se si sta scrivendo un metodo enumerabile (usando il rendimento restituito) o un metodo asincrono (usando asincrono e attendo). Altrimenti no.

Ciò significa che nei metodi regolari (non enumerabili e non asincroni), non si impostano le variabili locali, i parametri del metodo o i campi di istanza su null.

(Anche se stai implementando IDisposable.Dispose, non dovresti comunque impostare le variabili su null).

La cosa importante che dovremmo considerare è Static Fields .

I campi statici sono sempre oggetti radice , quindi sono sempre considerati "vivi" dal garbage collector. Se un campo statico fa riferimento a un oggetto che non è più necessario, dovrebbe essere impostato su null in modo che il Garbage Collector lo consideri idoneo per la raccolta.

L'impostazione di campi statici su null non ha senso se l'intero processo viene chiuso. L'intero heap sta per essere raccolto in quel punto, inclusi tutti gli oggetti root.

Conclusione:

Campi statici ; questo è tutto. Qualsiasi altra cosa è una perdita di tempo .


0

Credo che, in base alla progettazione degli implementatori GC, non è possibile accelerare GC con annullamento. Sono sicuro che preferirebbero che non ti preoccupassi di come / quando GC funziona - trattalo come questo onnipresente Essere protettivo e vegliare su e fuori per te ... (inchini a testa in giù, alza il pugno verso il cielo) .. .

Personalmente, ho spesso impostato esplicitamente le variabili su null quando ho finito con loro come una forma di auto documentazione. Non dichiaro, uso, quindi imposto su null in seguito - null immediatamente dopo che non sono più necessari. Sto dicendo esplicitamente: "Ho ufficialmente finito con te ... vattene ..."

L'annullamento è necessario in un linguaggio GC? No. È utile per il GC? Forse sì, forse no, non lo so per certo, in base alla progettazione non riesco davvero a controllarlo, e indipendentemente dalla risposta di oggi con questa versione o quella, le future implementazioni GC potrebbero cambiare la risposta al di fuori del mio controllo. Inoltre se / quando l'ottimizzazione è ottimizzata è poco più che un commento di fantasia se vuoi.

Immagino che se chiarisca le mie intenzioni al prossimo povero idiota che segue le mie orme e se "potrebbe" potenzialmente aiutare GC a volte, allora ne vale la pena. Principalmente mi fa sentire ordinato e chiaro, e a Mongo piace sentirmi ordinato e chiaro. :)

Lo guardo in questo modo: esistono linguaggi di programmazione per consentire alle persone di dare ad altre persone un'idea di intenti e di un compilatore una richiesta di lavoro su cosa fare - il compilatore converte quella richiesta in un linguaggio diverso (a volte diversi) per una CPU - la CPU (s) potrebbe dare un grido che lingua hai usato, le impostazioni della scheda, i commenti, le sottolineature stilistiche, i nomi delle variabili, ecc. - una CPU è tutto sul flusso di bit che gli dice quali registri e codici operativi e posizioni di memoria da modificare. Molte cose scritte nel codice non si convertono in ciò che viene consumato dalla CPU nella sequenza che abbiamo specificato. Il nostro C, C ++, C #, Lisp, Babel, assemblatore o qualsiasi altra cosa sia la teoria piuttosto che la realtà, scritta come una dichiarazione di lavoro. Quello che vedi non è quello che ottieni, sì, anche nel linguaggio assemblatore.

Capisco la mentalità delle "cose ​​inutili" (come le righe vuote) "non sono altro che rumore e ingombrare il codice". Sono stato io all'inizio della mia carriera; Lo capisco perfettamente. A questo punto mi chino verso ciò che rende il codice più chiaro. Non è che sto aggiungendo anche 50 righe di "rumore" ai miei programmi - sono alcune righe qui o là.

Ci sono eccezioni a qualsiasi regola. In scenari con memoria volatile, memoria statica, condizioni di gara, singleton, utilizzo di dati "non aggiornati" e tutto quel tipo di marciume, questo è diverso: è NECESSARIO gestire la propria memoria, bloccare e annullare come apostrofo perché la memoria non fa parte di l'Universo GC: spero che tutti lo capiscano. Il resto del tempo con i linguaggi GC'd è una questione di stile piuttosto che di necessità o di un aumento delle prestazioni garantito.

Alla fine della giornata assicurati di capire cosa è idoneo per GC e cosa no; bloccare, smaltire e annullare in modo appropriato; ceretta, ceretta; inspirare ed espirare; e per tutto il resto dico: se ti fa sentire bene, fallo. Il tuo chilometraggio può variare ... come dovrebbe ...


0

Penso che riportare qualcosa a null sia disordinato. Immagina uno scenario in cui l'oggetto su cui è impostato ora è esposto, ad esempio tramite proprietà. Ora in qualche modo un pezzo di codice usa accidentalmente questa proprietà dopo che l'articolo è stato eliminato, otterrai un'eccezione di riferimento null che richiede alcune indagini per capire esattamente cosa sta succedendo.

Credo che i usa e getta del framework consentano di gettare ObjectDisposedException che è più significativo. Non reimpostarli su null sarebbe meglio quindi per quel motivo.


-1

Alcuni oggetti suppongono il .dispose()metodo che forza la rimozione della risorsa dalla memoria.


11
No non lo fa; Dispose () non raccoglie l'oggetto: viene utilizzato per eseguire la pulizia deterministica, in genere rilasciando risorse non gestite.
Marc Gravell

1
Tenendo presente che il determinismo si applica solo alle risorse gestite, non a quelle non gestite (ad es. Memoria)
nicodemus13
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.