Avez-vous besoin de disposer des objets et de les mettre à zéro, ou le garbage collector les nettoiera-t-il lorsqu'ils sortiront du champ d'application?
310
Avez-vous besoin de disposer des objets et de les mettre à zéro, ou le garbage collector les nettoiera-t-il lorsqu'ils sortiront du champ d'application?
Dispose()
méthode! Il s'agit d'une variation subtile sur cette question, mais importante parce que l'objet étant disposé ne peut pas savoir s'il "sort de la portée" (l'appelDispose()
n'est pas une garantie). Plus ici: stackoverflow.com/questions/6757048/…Réponses:
Les objets seront nettoyés lorsqu'ils ne seront plus utilisés et lorsque le ramasse-miettes le jugera bon. Parfois, vous devrez peut-être définir un objet pour le
null
rendre hors de portée (comme un champ statique dont vous n'avez plus besoin de la valeur), mais dans l'ensemble, il n'est généralement pas nécessaire de le définir surnull
.Concernant l'élimination des objets, je suis d'accord avec @Andre. Si l'objet est,
IDisposable
il est judicieux de le supprimer lorsque vous n'en avez plus besoin, surtout si l'objet utilise des ressources non gérées. Ne pas disposer de ressources non gérées entraînera des fuites de mémoire .Vous pouvez utiliser l'
using
instruction pour supprimer automatiquement un objet une fois que votre programme quitte la portée de l'using
instruction.Qui est fonctionnellement équivalent à:
la source
if (obj != null) ((IDisposable)obj).Dispose();
IDisposable
. Ne pas disposer d'un objet ne provoquera généralement pas de fuite de mémoire sur une classe bien conçue. Lorsque vous travaillez avec des ressources non managées en C #, vous devez disposer d'un finaliseur qui libérera toujours les ressources non managées. Cela signifie qu'au lieu de désallouer les ressources lorsque cela doit être fait, il sera reporté au moment où le garbage collector finalise l'objet géré. Cela peut néanmoins causer de nombreux autres problèmes (tels que des verrous non libérés). Vous devriez jeter unIDisposable
bien!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émententIDisposable
lorsque vous avez fini de travailler avec eux. Normalement, vous utiliseriez unusing
bloc avec ces objets comme ceci: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:
En C #, cependant, vous obtenez une erreur de compilation:
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:
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.
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.
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.
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 classeMyClass
qui ne pointe vers rien. La seule façon de créer une instance d'une classe est d'utiliser l'new
opérateur: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.
la source
Comme d'autres l'ont dit, vous voulez certainement appeler
Dispose
si la classe implémenteIDisposable
. Je prends une position assez rigide à ce sujet. Certains prétendent que l' appel de puissanceDispose
surDataSet
, par exemple, est inutile parce qu'ils démontées et vu qu'il n'a pas fait quelque chose de significatif. Mais, je pense qu'il y a des erreurs dans cet argument.Lisez ceci pour un débat intéressant par des personnes respectées sur le sujet. Lisez ensuite mon raisonnement ici pourquoi je pense que Jeffery Richter est dans le mauvais camp.
Maintenant, pour savoir si vous devez ou non définir une référence
null
. La réponse est non. Permettez-moi d'illustrer mon propos avec le code suivant.Alors, quand pensez-vous que l'objet référencé par
a
est éligible pour la collecte? Si vous avez dit après l'appel,a = null
vous vous trompez. Si vous avez dit une fois laMain
méthode terminée, vous vous trompez également. La bonne réponse est qu'il est éligible à la collecte au cours de l'appel àDoSomething
. C'est vrai. Il est éligible avant la définition de la référencenull
et peut-être même avant la fin de l'appel àDoSomething
. En effet, le compilateur JIT peut reconnaître quand les références d'objet ne sont plus déréférencées même si elles sont toujours enracinées.la source
a
un champ membre privé dans une classe? Sia
n'est pas défini sur null, le GC n'a aucun moyen de savoir s'ila
sera à nouveau utilisé dans une méthode, non? Par conséquenta
, ne sera pas collecté tant que la classe entière ne sera pas collectée. Non?a
étiez un membre de la classe et que la classe contenanta
était toujours enracinée et en cours d'utilisation, elle resterait également. C'est un scénario où le définirnull
pourrait être bénéfique.Dispose
est important - il n'est pas possible d'invoquerDispose
(ou toute autre méthode non inlineable) sur un objet sans référence enracinée à celui-ci; appelerDispose
une fois que vous avez terminé à l'aide d'un objet garantit qu'une référence enracinée continuera d'exister pendant toute la durée de la dernière action effectuée sur celui-ci. L'abandon de toutes les références à un objet sans appelDispose
peut ironiquement avoir pour effet de libérer occasionnellement les ressources de l'objet trop tôt .Vous n'avez jamais besoin de définir des objets sur null en C #. Le compilateur et le runtime se chargeront de déterminer quand ils ne sont plus dans la portée.
Oui, vous devez supprimer les objets qui implémentent IDisposable.
la source
want
annuler dès que vous en avez terminé afin qu'il soit libre d'être récupéré.Je suis d'accord avec la réponse courante ici: oui, vous devez disposer et non, vous ne devez généralement pas définir la variable sur null ... mais je voulais souligner que disposer ne concerne PAS principalement la gestion de la mémoire. Oui, cela peut aider (et le fait parfois) avec la gestion de la mémoire, mais son objectif principal est de vous fournir une libération déterministe de ressources rares.
Par exemple, si vous ouvrez un port matériel (série par exemple), un socket TCP / IP, un fichier (en mode d'accès exclusif) ou même une connexion à une base de données, vous avez maintenant empêché tout autre code d'utiliser ces éléments jusqu'à ce qu'ils soient libérés. Dispose libère généralement ces articles (ainsi que GDI et autres poignées "os", etc., dont il existe des milliers, mais qui sont globalement limités). Si vous n'appelez pas dipose sur l'objet propriétaire et libérez explicitement ces ressources, essayez à nouveau d'ouvrir la même ressource à l'avenir (ou un autre programme le fera), cette tentative d'ouverture échouera car votre objet non disposé et non collecté a toujours l'élément ouvert . Bien sûr, lorsque le GC récupère l'élément (si le modèle Dispose a été correctement mis en œuvre), la ressource sera libérée ... mais vous ne savez pas quand cela le sera, alors vous ne le faites pas '' Je ne sais pas quand il est sûr de rouvrir cette ressource. Il s'agit du principal problème que Dispose contourne. Bien sûr, la libération de ces poignées libère souvent de la mémoire aussi, et ne jamais les libérer peut ne jamais libérer cette mémoire ... d'où toutes les discussions sur les fuites de mémoire ou les retards de nettoyage de la mémoire.
J'ai vu des exemples concrets de ce problème. Par exemple, j'ai vu des applications Web ASP.Net qui ne finissent pas par se connecter à la base de données (quoique pour de courtes périodes de temps ou jusqu'à ce que le processus du serveur Web soit redémarré) parce que le «pool de connexions du serveur SQL est plein» ... c.-à-d. , de nombreuses connexions ont été créées et non explicitement publiées dans un laps de temps si court qu'aucune nouvelle connexion ne peut être créée et de nombreuses connexions du pool, bien qu'elles ne soient pas actives, sont toujours référencées par des objets non déposés et non collectés et ainsi peuvent ' t être réutilisé. L'élimination correcte des connexions à la base de données, si nécessaire, garantit que ce problème ne se produit pas (du moins pas sauf si vous disposez d' un accès simultané très élevé).
la source
Si l'objet est implémenté
IDisposable
, alors oui, vous devez le supprimer. L'objet pourrait être accroché à des ressources natives (descripteurs de fichiers, objets OS) qui pourraient ne pas être libérées immédiatement sinon. Cela peut entraîner une famine des ressources, des problèmes de verrouillage de fichiers et d'autres bogues subtils qui pourraient autrement être évités.Voir également Implémentation d'une méthode de suppression sur MSDN.
la source
Dispose
sera appelée. De plus, si votre objet s'accroche à une ressource rare ou verrouille une ressource (par exemple un fichier), alors vous voudrez le libérer dès que possible. Attendre que le GC fasse cela n'est pas optimal.might
appeler, maiswill
appeler.S'ils implémentent l'interface IDisposable, vous devez les supprimer. Le ramasse-miettes s'occupe du reste.
EDIT: le mieux est d'utiliser la
using
commande lorsque vous travaillez avec des articles jetables:la source
Lorsqu'un objet implémente,
IDisposable
vous devez appelerDispose
(ouClose
, dans certains cas, cela appellera Dispose pour vous).Normalement, vous n'avez pas besoin de définir des objets
null
, car le GC sait qu'un objet ne sera plus utilisé.Il y a une exception lorsque je mets des objets à
null
. Lorsque je récupère un grand nombre d'objets (de la base de données) sur lesquels je dois travailler et les stocke dans une collection (ou un tableau). Lorsque le "travail" est terminé, je mets l'objet ànull
, car le GC ne sait pas que j'ai fini de travailler avec.Exemple:
la source
Normalement, il n'est pas nécessaire de définir les champs sur null. Cependant, je recommanderais toujours de supprimer les ressources non gérées.
Par expérience, je vous conseille également de faire ce qui suit:
J'ai rencontré des problèmes très difficiles à trouver qui étaient le résultat direct du non-respect des conseils ci-dessus.
Un bon endroit pour le faire est dans Dispose (), mais le plus tôt est généralement mieux.
En général, s'il existe une référence à un objet, le garbage collector (GC) peut prendre quelques générations de plus pour comprendre qu'un objet n'est plus utilisé. Pendant tout ce temps, l'objet reste en mémoire.
Cela peut ne pas être un problème tant que vous n'avez pas constaté que votre application utilise beaucoup plus de mémoire que vous ne le pensez. Lorsque cela se produit, connectez un profileur de mémoire pour voir quels objets ne sont pas nettoyés. La définition de champs référençant d'autres objets à null et la suppression des collections lors de leur élimination peuvent vraiment aider le GC à déterminer quels objets il peut supprimer de la mémoire. Le GC récupérera la mémoire utilisée plus rapidement, rendant votre application beaucoup moins gourmande en mémoire et plus rapide.
la source
Appelez toujours disposer. Cela ne vaut pas le risque. Les grandes applications d'entreprise gérées doivent être traitées avec respect. Aucune hypothèse ne peut être faite sinon elle reviendra vous mordre.
N'écoutez pas leppie.
De nombreux objets n'implémentent pas réellement IDisposable, vous n'avez donc pas à vous en préoccuper. S'ils sortent vraiment du cadre, ils seront automatiquement libérés. De plus, je n'ai jamais rencontré de situation où j'ai dû mettre quelque chose à zéro.
Une chose qui peut arriver, c'est que beaucoup d'objets peuvent être maintenus ouverts. Cela peut augmenter considérablement l'utilisation de la mémoire de votre application. Parfois, il est difficile de déterminer s'il s'agit réellement d'une fuite de mémoire ou si votre application fait simplement beaucoup de choses.
Les outils de profil de mémoire peuvent aider avec des choses comme ça, mais cela peut être délicat.
De plus, désabonnez-vous toujours des événements qui ne sont pas nécessaires. Soyez également prudent avec la liaison et les contrôles WPF. Pas une situation habituelle, mais je suis tombé sur une situation où j'avais un contrôle WPF qui était lié à un objet sous-jacent. L'objet sous-jacent était volumineux et occupait une grande quantité de mémoire. Le contrôle WPF était remplacé par une nouvelle instance, et l'ancien était toujours en attente pour une raison quelconque. Cela a provoqué une fuite de mémoire importante.
Dans Hindsite, le code a été mal écrit, mais le fait est que vous voulez vous assurer que les choses qui ne sont pas utilisées sortent du cadre. Cela a pris beaucoup de temps à trouver avec un profileur de mémoire car il est difficile de savoir ce qui est valide en mémoire et ce qui ne devrait pas y être.
la source
Je dois aussi répondre. Le JIT génère des tableaux avec le code à partir de son analyse statique de l'utilisation des variables. Ces entrées de table sont les "GC-Roots" dans le cadre de pile actuel. Au fur et à mesure que le pointeur d'instruction avance, ces entrées de table deviennent invalides et donc prêtes pour la récupération de place. Par conséquent: s'il s'agit d'une variable de portée, vous n'avez pas besoin de la définir sur null - le GC collectera l'objet. S'il s'agit d'un membre ou d'une variable statique, vous devez le définir sur null
la source