c# - pattern - vb.net dispose




Hai bisogno di disporre di oggetti e impostarli su null? (8)

Avete bisogno di smaltire oggetti e di impostarli su null, o il garbage collector li pulirà quando andranno fuori campo?



Chiamare sempre. Non vale il rischio. Le grandi applicazioni aziendali gestite dovrebbero essere trattate con rispetto. Non si possono fare ipotesi, altrimenti tornerà a morderti.

Non ascoltare leppie.

Un sacco di oggetti non implementano effettivamente IDisposable, quindi non devi preoccuparti di loro. Se passano davvero fuori dal campo di applicazione, verranno automaticamente liberati. Inoltre non ho mai incontrato la situazione in cui ho dovuto impostare qualcosa su null.

Una cosa che può succedere è che molti oggetti possono essere tenuti aperti. Ciò può aumentare notevolmente l'utilizzo della memoria della tua applicazione. A volte è difficile capire se si tratta effettivamente di una perdita di memoria o se la tua applicazione sta facendo un sacco di cose.

Gli strumenti del profilo di memoria possono aiutare con cose del genere, ma può essere complicato.

Inoltre, disiscriversi sempre dagli eventi che non sono necessari. Fai attenzione anche al binding e ai controlli di WPF. Non è una situazione normale, ma mi sono imbattuto in una situazione in cui avevo un controllo WPF che era legato a un oggetto sottostante. L'oggetto sottostante era grande e occupava una grande quantità di memoria. Il controllo WPF veniva sostituito con una nuova istanza e il vecchio era ancora in giro per qualche motivo. Ciò ha causato una perdita di memoria di grandi dimensioni.

In hindsite il codice è stato scritto male, ma il punto è che si vuole essere sicuri che le cose che non vengono usate vadano fuori dal campo di applicazione. Quello ha richiesto molto tempo per essere trovato con un profiler di memoria, poiché è difficile sapere quali elementi della memoria sono validi e cosa non dovrebbe esserci.


Devo rispondere anch'io. Il JIT genera tabelle insieme al codice dalla sua analisi statica dell'uso delle variabili. Queste voci di tabella sono "GC-Roots" nel frame dello stack corrente. Mentre il puntatore delle istruzioni avanza, quelle voci della tabella diventano non valide e quindi pronte per la garbage collection. Pertanto: se si tratta di una variabile con ambito, non è necessario impostarla su null - il GC raccoglierà l'oggetto. Se è un membro o una variabile statica, devi impostarlo su null


Gli oggetti non escono mai dall'ambito in C # come fanno in C ++. Sono gestiti automaticamente dal Garbage Collector quando non vengono più utilizzati. Questo è un approccio più complicato del C ++ in cui lo scopo di una variabile è interamente deterministico. Il garbage collector CLR passa attivamente attraverso tutti gli oggetti che sono stati creati e funziona se vengono utilizzati.

Un oggetto può andare "fuori ambito" in una funzione, ma se viene restituito il suo valore, GC dovrebbe verificare se la funzione chiamante mantiene o meno il valore restituito.

L'impostazione dei riferimenti a oggetti null non è necessaria poiché la garbage collection funziona calcolando quali oggetti vengono referenziati da altri oggetti.

In pratica, non devi preoccuparti della distruzione, funziona solo ed è fantastico :)

Dispose deve essere chiamato su tutti gli oggetti che implementano IDisposable quando hai finito di lavorare con loro. Normalmente useresti un blocco using quegli oggetti come questi:

using (var ms = new MemoryStream()) {
  //...
}

MODIFICA Su ambito variabile. Craig ha chiesto se l'ambito della variabile ha qualche effetto sulla durata dell'oggetto. Per spiegare correttamente questo aspetto di CLR, dovrò spiegare alcuni concetti di C ++ e C #.

Ambito della portata reale

In entrambe le lingue, la variabile può essere utilizzata solo nello stesso ambito definito - classe, funzione o blocco di istruzioni racchiuso tra parentesi graffe. La sottile differenza, tuttavia, è che in C #, le variabili non possono essere ridefinite in un blocco nidificato.

In C ++, questo è perfettamente legale:

int iVal = 8;
//iVal == 8
if (iVal == 8){
    int iVal = 5;
    //iVal == 5
}
//iVal == 8

In C #, tuttavia, si ottiene un errore del compilatore:

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
}

Questo ha senso se si guarda l'MSIL generato - tutte le variabili utilizzate dalla funzione sono definite all'inizio della funzione. Dai un'occhiata a questa funzione:

public static void Scope() {
    int iVal = 8;
    if(iVal == 8) {
        int iVal2 = 5;
    }
}

Di seguito è riportato l'IL generato. Notare che iVal2, che è definito all'interno del blocco if, è effettivamente definito a livello di funzione. In pratica ciò significa che C # ha solo ambito di classe e livello di funzione per quanto riguarda la durata variabile.

.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

Ambito C ++ e durata dell'oggetto

Ogni volta che una variabile C ++, allocata nello stack, esce dal campo di applicazione viene distrutta. Ricorda che in C ++ puoi creare oggetti nello stack o nell'heap. Quando li crei nello stack, una volta che l'esecuzione lascia l'ambito, vengono estratti dallo stack e vengono distrutti.

if (true) {
  MyClass stackObj; //created on the stack
  MyClass heapObj = new MyClass(); //created on the heap
  obj.doSomething();
} //<-- stackObj is destroyed
//heapObj still lives

Quando gli oggetti C ++ vengono creati nell'heap, devono essere esplicitamente distrutti, altrimenti si tratta di una perdita di memoria. Non c'è nessun problema con le variabili dello stack.

Durata oggetto C #

In CLR, gli oggetti (cioè i tipi di riferimento) vengono sempre creati nell'heap gestito. Ciò è ulteriormente rafforzato dalla sintassi della creazione dell'oggetto. Considera questo snippet di codice.

MyClass stackObj;

In C ++ questo creerebbe un'istanza su MyClass nello stack e chiamerebbe il suo costruttore predefinito. In C # creerebbe un riferimento alla classe MyClass che non punta a nulla. L'unico modo per creare un'istanza di una classe è usando il new operatore:

MyClass stackObj = new MyClass();

In un certo senso, gli oggetti C # sono molto simili agli oggetti creati usando la new sintassi in C ++ - vengono creati nell'heap ma a differenza degli oggetti C ++, sono gestiti dal runtime, quindi non devi preoccuparti di distruggerli.

Poiché gli oggetti sono sempre nell'heap, il fatto che i riferimenti oggetto (cioè i puntatori) vadano fuori ambito diventa discutibile. Ci sono più fattori coinvolti nel determinare se un oggetto deve essere raccolto rispetto alla semplice presenza di riferimenti all'oggetto.

Riferimenti all'oggetto C #

Jon Skeet ha confrontato i riferimenti a oggetti in Java con pezzi di stringa che sono collegati al fumetto, che è l'oggetto. La stessa analogia si applica ai riferimenti all'oggetto C #. Semplicemente puntano a una posizione dell'heap che contiene l'oggetto. Quindi, impostandolo su null non ha alcun effetto immediato sulla durata dell'oggetto, il fumetto continua ad esistere, fino a quando il GC non lo "pop".

Continuando l'analogia con il pallone, sembrerebbe logico che una volta che il pallone non ha legacci attaccati, può essere distrutto. In effetti, questo è esattamente il modo in cui gli oggetti conteggiati di riferimento funzionano in lingue non gestite. Tranne che questo approccio non funziona molto bene per i riferimenti circolari. Immagina due palloncini attaccati insieme da una stringa, ma nessuno dei due palloncini ha una stringa. Con semplici regole di conteggio dei ref, entrambi continuano ad esistere, anche se l'intero gruppo di palloncini è "orfano".

Gli oggetti .NET sono molto simili a palloni di elio sotto un tetto. Quando il tetto si apre (GC corre), i palloncini non utilizzati galleggiano via, anche se potrebbero esserci gruppi di palloncini legati insieme.

.NET GC utilizza una combinazione di GC generazionale e mark e sweep. L'approccio generazionale implica il runtime che privilegia l'ispezione degli oggetti che sono stati allocati più recentemente, poiché è più probabile che non siano utilizzati e mark e sweep coinvolge il runtime che attraversa l'intero grafico dell'oggetto e funziona se ci sono gruppi di oggetti non utilizzati. Ciò riguarda in modo adeguato il problema della dipendenza circolare.

Inoltre, .NET GC gira su un altro thread (il cosiddetto thread di finalizzazione) poiché ha un bel po 'di cose da fare e farlo sul thread principale interromperà il tuo programma.


Non hai mai bisogno di impostare oggetti su null in C #. Il compilatore e il runtime si prenderanno cura di capire quando non sono più nel campo di applicazione.

Sì, è necessario smaltire oggetti che implementano IDisposable.


Normalmente, non è necessario impostare i campi su null. Consiglio sempre comunque di smaltire risorse non gestite.

Per esperienza ti consiglio anche di fare quanto segue:

  • Cancellati dagli eventi se non ne hai più bisogno.
  • Imposta qualsiasi campo in cui un delegato o un'espressione sono nulli se non è più necessario.

Mi sono imbattuto in alcuni problemi molto difficili da trovare che erano il risultato diretto di non seguire il consiglio di cui sopra.

Un buon posto per farlo è in Dispose (), ma prima è solitamente meglio.

In generale, se esiste un riferimento a un oggetto, il Garbage Collector (GC) potrebbe impiegare un paio di generazioni per capire che un oggetto non è più in uso. Nel frattempo l'oggetto rimane in memoria.

Questo potrebbe non essere un problema finché non trovi che la tua app utilizza molta più memoria di quanto ti aspetti. Quando ciò accade, collega un profiler di memoria per vedere quali oggetti non vengono ripuliti. L'impostazione dei campi che fanno riferimento a altri oggetti su null e la cancellazione di raccolte su smaltimento può davvero aiutare il GC a capire quali oggetti può rimuovere dalla memoria. Il GC recupererà la memoria utilizzata più velocemente rendendo la tua app molto meno affamata di memoria e più veloce.


Se implementano l'interfaccia IDisposable, dovresti eliminarli. Il netturbino si prenderà cura di tutto il resto.

EDIT: la cosa migliore è usare il comando using quando si lavora con articoli usa e getta:

using(var con = new SqlConnection("..")){ ...

Se l'oggetto implementa IDisposable , allora sì, dovresti eliminarlo. L'oggetto potrebbe essere sospeso a risorse native (handle di file, oggetti del sistema operativo) che potrebbero non essere liberati immediatamente altrimenti. Ciò può portare alla carenza di risorse, a problemi di blocco dei file e altri bug sottili che potrebbero altrimenti essere evitati.

Vedi anche Implementazione di un metodo di smaltimento su MSDN.







dispose