Les objets ne sortent jamais de la portée en C # comme ils le font en C ++. Ils sont traités automatiquement par le garbage collector lorsqu'ils ne sont plus utilisés. Il s'agit d'une approche plus compliquée que C ++ où la portée d'une variable est entièrement déterministe. Le ramasse-miettes CLR passe activement en revue tous les objets qui ont été créés et détermine s'ils sont utilisés.
Un objet peut sortir "hors de portée" dans une fonction mais si sa valeur est renvoyée, GC vérifiera si la fonction appelante conserve ou non la valeur de retour.
Il null
n'est pas nécessaire de définir des références d'objet car le garbage collection fonctionne en déterminant quels objets sont référencés par d'autres objets.
En pratique, vous n'avez pas à vous soucier de la destruction, cela fonctionne et c'est génial :)
Dispose
doit être appelé sur tous les objets qui implémentent IDisposable
lorsque vous avez fini de travailler avec eux. Normalement, vous utiliseriez un using
bloc avec ces objets comme ceci:
using (var ms = new MemoryStream()) {
//...
}
EDIT Sur portée variable. Craig a demandé si la portée variable a un effet sur la durée de vie de l'objet. Pour expliquer correctement cet aspect de CLR, je vais devoir expliquer quelques concepts de C ++ et C #.
Portée variable réelle
Dans les deux langues, la variable ne peut être utilisée que dans la même portée que celle définie - classe, fonction ou bloc d'instructions entouré d'accolades. La différence subtile, cependant, est qu'en C #, les variables ne peuvent pas être redéfinies dans un bloc imbriqué.
En C ++, c'est parfaitement légal:
int iVal = 8;
//iVal == 8
if (iVal == 8){
int iVal = 5;
//iVal == 5
}
//iVal == 8
En C #, cependant, vous obtenez une erreur de compilation:
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
}
Cela a du sens si vous regardez le MSIL généré - toutes les variables utilisées par la fonction sont définies au début de la fonction. Jetez un œil à cette fonction:
public static void Scope() {
int iVal = 8;
if(iVal == 8) {
int iVal2 = 5;
}
}
Ci-dessous se trouve l'IL généré. Notez que iVal2, qui est défini à l'intérieur du bloc if est réellement défini au niveau de la fonction. En fait, cela signifie que C # n'a qu'une portée de niveau classe et fonction en ce qui concerne la durée de vie variable.
.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
Portée C ++ et durée de vie de l'objet
Chaque fois qu'une variable C ++, allouée sur la pile, sort du cadre, elle est détruite. N'oubliez pas qu'en C ++, vous pouvez créer des objets sur la pile ou sur le tas. Lorsque vous les créez sur la pile, une fois que l'exécution quitte la portée, ils sont extraits de la pile et détruits.
if (true) {
MyClass stackObj; //created on the stack
MyClass heapObj = new MyClass(); //created on the heap
obj.doSomething();
} //<-- stackObj is destroyed
//heapObj still lives
Lorsque des objets C ++ sont créés sur le tas, ils doivent être explicitement détruits, sinon c'est une fuite de mémoire. Pas un tel problème avec les variables de pile cependant.
Durée de vie de l'objet C #
Dans CLR, les objets (c'est-à-dire les types de référence) sont toujours créés sur le tas géré. Ceci est encore renforcé par la syntaxe de création d'objet. Considérez cet extrait de code.
MyClass stackObj;
En C ++, cela créerait une instance MyClass
sur la pile et appellerait son constructeur par défaut. En C #, cela créerait une référence à la classe MyClass
qui ne pointe vers rien. La seule façon de créer une instance d'une classe est d'utiliser l' new
opérateur:
MyClass stackObj = new MyClass();
D'une certaine manière, les objets C # ressemblent beaucoup à des objets créés en utilisant la new
syntaxe en C ++ - ils sont créés sur le tas mais contrairement aux objets C ++, ils sont gérés par le runtime, vous n'avez donc pas à vous soucier de les détruire.
Étant donné que les objets sont toujours sur le tas, le fait que les références d'objet (c'est-à-dire les pointeurs) sortent de la portée devient inutile. Il y a plus de facteurs impliqués pour déterminer si un objet doit être collecté que la simple présence de références à l'objet.
Références d'objets C #
Jon Skeet a comparé les références d'objets en Java aux morceaux de chaîne attachés au ballon, qui est l'objet. La même analogie s'applique aux références d'objets C #. Ils pointent simplement vers un emplacement du tas qui contient l'objet. Ainsi, le définir sur null n'a pas d'effet immédiat sur la durée de vie de l'objet, le ballon continue d'exister, jusqu'à ce que le GC le "fasse éclater".
En poursuivant l'analogie avec le ballon, il semblerait logique qu'une fois que le ballon n'a pas de cordes, il peut être détruit. En fait, c'est exactement ainsi que fonctionnent les objets comptés par référence dans les langages non gérés. Sauf que cette approche ne fonctionne pas très bien pour les références circulaires. Imaginez deux ballons qui sont attachés ensemble par une chaîne, mais aucun ballon n'a de chaîne pour autre chose. Selon de simples règles de comptage des références, ils continuent d'exister tous les deux, même si l'ensemble du groupe de ballons est "orphelin".
Les objets .NET ressemblent beaucoup à des ballons à l'hélium sous un toit. Lorsque le toit s'ouvre (GC fonctionne) - les ballons inutilisés flottent, même s'il peut y avoir des groupes de ballons attachés ensemble.
Le GC .NET utilise une combinaison de GC générationnel et de marquage et balayage. L'approche générationnelle implique que le runtime favorise l'inspection des objets qui ont été alloués le plus récemment, car ils sont plus susceptibles d'être inutilisés et le marquage et le balayage impliquent que le runtime parcourt le graphique d'objet entier et détermine s'il existe des groupes d'objets inutilisés. Cela traite adéquatement le problème de dépendance circulaire.
En outre, .NET GC s'exécute sur un autre thread (ce que l'on appelle le thread de finalisation) car il a beaucoup à faire et le faire sur le thread principal interromprait votre programme.