Il est important de séparer l'élimination de la collecte des ordures. Ce sont des choses complètement séparées, avec un point commun sur lequel je reviendrai dans une minute.
Dispose
, ramassage des ordures et finalisation
Lorsque vous écrivez une using
instruction, c'est simplement du sucre syntaxique pour un bloc try / finally, qui Dispose
est appelé même si le code dans le corps de l' using
instruction lève une exception. Cela ne signifie pas que l'objet est ramassé à la fin du bloc.
L'élimination concerne les ressources non gérées ( ressources non-mémoire). Il peut s'agir de descripteurs d'interface utilisateur, de connexions réseau, de descripteurs de fichiers, etc. Ce sont des ressources limitées, vous voulez donc généralement les libérer dès que vous le pouvez. Vous devez implémenter IDisposable
chaque fois que votre type "possède" une ressource non gérée, soit directement (généralement via un IntPtr
) ou indirectement (par exemple via a Stream
, a, SqlConnection
etc.).
Le ramassage des ordures lui-même ne concerne que la mémoire - avec une petite torsion. Le garbage collector est capable de trouver des objets qui ne peuvent plus être référencés et de les libérer. Cependant, il ne recherche pas de déchets tout le temps - seulement quand il détecte qu'il en a besoin (par exemple, si une "génération" du tas manque de mémoire).
La torsion est la finalisation . Le garbage collector conserve une liste d'objets qui ne sont plus accessibles, mais qui ont un finaliseur (écrit comme ~Foo()
en C #, quelque peu déroutant - ils ne ressemblent en rien aux destructeurs C ++). Il exécute les finaliseurs sur ces objets, juste au cas où ils auraient besoin de faire un nettoyage supplémentaire avant que leur mémoire ne soit libérée.
Les finaliseurs sont presque toujours utilisés pour nettoyer les ressources dans le cas où l'utilisateur du type a oublié de s'en débarrasser de manière ordonnée. Ainsi, si vous ouvrez un FileStream
mais oubliez d'appeler Dispose
ou Close
, le finaliseur finira par libérer le descripteur de fichier sous-jacent pour vous. Dans un programme bien écrit, les finaliseurs ne devraient presque jamais se déclencher à mon avis.
Définition d'une variable sur null
Un petit point sur la définition d'une variable sur null
- ce n'est presque jamais nécessaire pour le garbage collection. Vous voudrez peut-être parfois le faire s'il s'agit d'une variable membre, bien que d'après mon expérience, il est rare qu'une "partie" d'un objet ne soit plus nécessaire. Lorsqu'il s'agit d'une variable locale, le JIT est généralement assez intelligent (en mode release) pour savoir quand vous n'allez plus utiliser une référence. Par exemple:
StringBuilder sb = new StringBuilder();
sb.Append("Foo");
string x = sb.ToString();
// The string and StringBuilder are already eligible
// for garbage collection here!
int y = 10;
DoSomething(y);
// These aren't helping at all!
x = null;
sb = null;
// Assume that x and sb aren't used here
Le seul moment où il peut être intéressant de définir une variable locale null
est lorsque vous êtes dans une boucle, et certaines branches de la boucle doivent utiliser la variable, mais vous savez que vous avez atteint un point auquel vous ne l'avez pas. Par exemple:
SomeObject foo = new SomeObject();
for (int i=0; i < 100000; i++)
{
if (i == 5)
{
foo.DoSomething();
// We're not going to need it again, but the JIT
// wouldn't spot that
foo = null;
}
else
{
// Some other code
}
}
Implémentation d'IDisposable / finalizers
Alors, vos propres types devraient-ils implémenter des finaliseurs? Presque certainement pas. Si vous ne détenez qu'indirectement des ressources non gérées (par exemple, vous avez une FileStream
variable en tant que membre), alors l'ajout de votre propre finaliseur n'aidera pas: le flux sera presque certainement éligible pour le ramasse-miettes lorsque votre objet est, donc vous pouvez simplement compter sur FileStream
avoir un finaliseur (si nécessaire - il peut faire référence à autre chose, etc.). Si vous voulez conserver une ressource non gérée «presque» directement, SafeHandle
c'est votre ami - cela prend un peu de temps pour y aller, mais cela signifie que vous n'aurez presque plus jamais besoin d'écrire un finaliseur . Vous ne devriez généralement avoir besoin d'un finaliseur que si vous avez une poignée vraiment directe sur une ressource (an IntPtr
) et que vous devriez chercher à passer àSafeHandle
dès que vous le pouvez. (Il y a deux liens ici - lisez les deux, idéalement.)
Joe Duffy a un très long ensemble de directives concernant les finaliseurs et IDisposable (co-écrit avec beaucoup de gens intelligents) qui valent la peine d'être lus. Il vaut la peine de savoir que si vous scellez vos classes, cela vous facilite beaucoup la vie: le modèle de substitution Dispose
pour appeler une nouvelle Dispose(bool)
méthode virtuelle , etc. n'est pertinent que lorsque votre classe est conçue pour l'héritage.
Cela a été un peu compliqué, mais veuillez demander des éclaircissements où vous en aimeriez :)