Définir un objet sur null vs Dispose()
Je suis fasciné par la façon dont le CLR et le GC fonctionnent (je travaille à élargir mes connaissances à ce sujet en lisant CLR via C#, les livres/messages de Jon Skeet, et plus encore).
Quoi Qu'il en soit, Quelle est la différence entre dire:
MyClass myclass = new MyClass();
myclass = null;
Ou, en faisant implémenter MyClass IDisposable et un destructeur et en appelant Dispose ()?
Aussi, si j'ai un bloc de code avec une instruction using (par exemple ci-dessous), si je traverse le code et quitte le bloc using, l'objet est-il éliminé alors ou quand un la collecte des ordures se produit? Que se passerait-il si j'appelle Dispose() dans le bloc using anyay?
using (MyDisposableObj mydispobj = new MyDisposableObj())
{
}
Les classes de flux (par exemple BinaryWriter) ont une méthode Finalize? Pourquoi je voudrais utiliser ça?
3 réponses
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 auquel je reviendrai dans une minute.
Dispose
, Collecte des ordures et finalisation
Lorsque vous écrivez une instruction using
, c'est simplement du sucre syntaxique pour un bloc try/finally de sorte que Dispose
est appelé même si le code dans le corps de l'instruction using
lève une exception. Cela ne signifie pas que l'objet est garbage collected at la fin du bloc.
L'élimination est d'environ ressources non gérées (ressources non-mémoire). Ceux-ci peuvent être des poignées D'interface utilisateur, des connexions réseau, des poignées de fichiers, etc. Ce sont des ressources limitées, donc vous voulez 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 un Stream
, un SqlConnection
etc.).
La collecte des ordures elle-même ne concerne que la mémoire-avec un petit twist. Le garbage collector est capable de trouver des objets qui ne peuvent plus être référencés et de les libérer. Il ne cherche pas les ordures 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 finalisation . Le garbage collector conserve une liste d'objets qui ne sont plus accessibles, mais qui ont un finaliseur (écrit ~Foo()
en C#, quelque peu confus-ils ne sont rien comme C++ destructeur). 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.
Finaliseurs sont presque toujours utilisé pour nettoyer les ressources dans le cas où l'utilisateur du type a oublié d'en disposer d'une manière ordonnée. Donc, si vous ouvrez un FileStream
mais oubliez d'appeler Dispose
ou Close
, le finaliseur finira par libérer le handle de fichier sous-jacent pour vous. Dans un programme bien écrit, les finaliseurs ne devraient presque jamais tirer dans mon opinion.
Définition d'une variable de null
Un petit point sur la définition d'une variable à null
- ce n'est presque jamais nécessaire pour la récupération de place. Vous voudrez peut-être parfois le faire si c'est une variable membre, bien que dans mon expérience il soit rare que "partie" d'un objet ne soit plus nécessaire. Quand 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. Pour 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
La seule fois où peut valoir la peine de définir une variable locale sur 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 le faites 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
}
}
La mise en Œuvre de IDisposable/finaliseurs
Donc, vos propres types devraient-ils implémenter des finaliseurs? Presque certainement pas. Si vous ne possédez que indirectement des ressources non gérées (par exemple, vous avez un FileStream
en tant que variable membre), puis ajouter votre propre finalizer n'aidera pas: le flux sera presque certainement admissible à la récupération de place lorsque votre objet l'est, donc vous pouvez simplement compter sur FileStream
ayant un finalizer (si nécessaire - il peut faire référence à autre chose, etc.). Si vous voulez tenir une ressource non gérée "presque" directement, SafeHandle
est votre ami-il faut un peu de temps pour y aller, mais cela signifie que vous presque plus jamais besoin d'écrire un finaliseur. Vous devriez habituellement seulement besoin d'un finaliseur si vous avez un très directe de la poignée d'une ressource (un IntPtr
) et vous devriez regarder pour passer à SafeHandle
dès que vous le pouvez. (Il y a deux liens là - bas-lisez les deux, idéalement.)
Joe Duffy a un très long ensemble de directives autour des finaliseurs et IDisposable (co-écrit avec beaucoup de gens intelligents) qui valent la peine d'être lus. Il vaut la peine d'être conscient que si vous scellez vos classes, cela rend la vie beaucoup plus facile: le modèle de substitution Dispose
pour appeler un nouveau virtuel Dispose(bool)
la méthode etc n'est pertinente que lorsque votre classe est conçue pour l'héritage.
Cela a été un peu une randonnée, mais veuillez demander des éclaircissements où vous en souhaitez:)
Lorsque vous disposez d'un objet, les ressources sont libérées. Lorsque vous attribuez null à une variable, vous modifiez simplement une référence.
myclass = null;
Après avoir exécuté ceci, l'objet auquel myclass faisait référence existe toujours, et continuera jusqu'à ce que le GC se déplace pour le nettoyer. Si Dispose est explicitement appelé, ou il est dans un bloc using, toutes les ressources seront libérées dès que possible.
Les deux opérations n'ont pas grand-chose à voir l'une avec l'autre. Lorsque vous définissez une référence à null, il le fait simplement. Cela n'affecte pas du tout la classe référencée. Votre variable ne pointe tout simplement plus vers l'objet auquel elle était habituée, mais l'objet lui-même est inchangé.
Lorsque vous appelez Dispose(), c'est un appel de méthode sur l'objet lui-même. Quelle que soit la méthode Dispose, est maintenant fait sur l'objet. Mais cela n'affecte pas votre référence à l'objet.
Le seul la zone de chevauchement est que Quand il n'y a plus de références à un objet, il va éventuellement récupérer les ordures. Et si la classe implémente L'interface IDisposable, Dispose () sera appelée sur l'objet avant qu'il ne soit collecté.
Mais cela n'arrivera pas immédiatement après avoir défini votre référence sur null, pour deux raisons. Premièrement, d'autres références peuvent exister, donc il n'y aura pas encore de déchets collectés, et deuxièmement, même si c'était la dernière référence, donc, il est maintenant prêt à être collecté, rien ne se passera jusqu'à ce que le garbage collector décide de supprimer l'objet.
Appeler Dispose () sur un objet ne "tue" en aucune façon l'objet. Il est couramment utilisé pour nettoyer afin que L'objet puisse être supprimé en toute sécurité par la suite, mais finalement, il N'y a rien de magique à disposer, c'est juste une méthode de classe.