Gli oggetti non escono mai dall'ambito in C # come in C ++. Vengono gestiti automaticamente dal Garbage Collector quando non vengono più utilizzati. Questo è un approccio più complicato del C ++ in cui l'ambito di una variabile è del tutto deterministico. Il Garbage Collector di CLR passa attivamente attraverso tutti gli oggetti che sono stati creati e determina se vengono utilizzati.
Un oggetto può "uscire dall'ambito" in una funzione ma se il suo valore viene restituito, GC controllerebbe se la funzione chiamante mantiene o meno il valore restituito.
L'impostazione dei riferimenti agli oggetti su null
non è necessaria poiché la garbage collection funziona determinando quali oggetti fanno riferimento a altri oggetti.
In pratica, non devi preoccuparti della distruzione, funziona e va benissimo :)
Dispose
deve essere chiamato su tutti gli oggetti che implementano IDisposable
quando hai finito di lavorare con loro. Normalmente useresti un using
blocco con quegli oggetti in questo modo:
using (var ms = new MemoryStream()) {
//...
}
EDIT su ambito variabile. Craig ha chiesto se l'ambito variabile ha alcun effetto sulla durata dell'oggetto. Per spiegare correttamente quell'aspetto di CLR, dovrò spiegare alcuni concetti di C ++ e C #.
Portata variabile effettiva
In entrambe le lingue la variabile può essere utilizzata solo nello stesso ambito in cui è stata definita: classe, funzione o un 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, viene visualizzato 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 osserva 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 è l'IL generato. Si noti che iVal2, che è definito all'interno del blocco if, è effettivamente definito a livello di funzione. In pratica ciò significa che C # ha solo un 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 dall'ambito, 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 espulsi 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 sull'heap, devono essere esplicitamente distrutti, altrimenti si tratta di una perdita di memoria. Nessun problema con le variabili dello stack però.
Durata dell'oggetto C #
In CLR, gli oggetti (ovvero i tipi di riferimento) vengono sempre creati sull'heap gestito. Ciò è ulteriormente rafforzato dalla sintassi della creazione di oggetti. Considera questo frammento di codice.
MyClass stackObj;
In C ++ questo creerebbe un'istanza 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 è utilizzando l' new
operatore:
MyClass stackObj = new MyClass();
In un certo senso, gli oggetti C # sono molto simili agli oggetti creati utilizzando la new
sintassi in C ++: sono creati nell'heap ma, diversamente dagli oggetti C ++, sono gestiti dal runtime, quindi non devi preoccuparti di distruggerli.
Poiché gli oggetti sono sempre nell'heap, il fatto che i riferimenti agli oggetti (ovvero i puntatori) escano dall'ambito diventa discutibile. Ci sono più fattori coinvolti nel determinare se un oggetto deve essere raccolto oltre alla semplice presenza di riferimenti all'oggetto.
Riferimenti per oggetti C #
Jon Skeet ha confrontato i riferimenti a oggetti in Java con pezzi di stringa collegati al fumetto, che è l'oggetto. La stessa analogia si applica ai riferimenti agli oggetti C #. Indicano semplicemente una posizione dell'heap che contiene l'oggetto. Pertanto, impostandolo su null non ha alcun effetto immediato sulla durata dell'oggetto, il fumetto continua a esistere, fino a quando il GC non lo "pop".
Continuando verso l'analogia del palloncino, sembrerebbe logico che una volta che il palloncino non ha stringhe attaccate ad esso, possa essere distrutto. In effetti, questo è esattamente il modo in cui gli oggetti contati di riferimento funzionano in linguaggi non gestiti. Solo che questo approccio non funziona molto bene per i riferimenti circolari. Immagina due palloncini che sono attaccati insieme da una corda ma nessuno dei due palloncini ha una corda per nient'altro. In base a semplici regole di conteggio degli ref, entrambi continuano ad esistere, anche se l'intero gruppo di palloncini è "orfano".
Gli oggetti .NET sono molto simili ai palloncini ad elio sotto un tetto. Quando il tetto si apre (GC esegue) - 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 and sweep. L'approccio generazionale prevede il runtime che favorisce l'ispezione degli oggetti che sono stati allocati più di recente, poiché è più probabile che siano inutilizzati e contrassegna e sweep comporta il runtime che passa attraverso l'intero grafico degli oggetti e risolve se ci sono gruppi di oggetti che non sono utilizzati. Questo affronta adeguatamente il problema della dipendenza circolare.
Inoltre, .NET GC funziona su un altro thread (il cosiddetto thread finalizzatore) in quanto ha un bel po 'da fare e farlo sul thread principale interromperà il programma.