Le modèle .NET IDisposable implique que si vous écrivez un finaliseur et implémentez IDisposable, votre finaliseur doit appeler explicitement Dispose. C'est logique, et c'est ce que j'ai toujours fait dans les rares situations où un finaliseur est justifié.
Cependant, que se passe-t-il si je fais juste ceci:
class Foo : IDisposable
{
public void Dispose(){ CloseSomeHandle(); }
}
et n'implémentez pas de finaliseur ou quoi que ce soit. Le framework appellera-t-il la méthode Dispose pour moi?
Oui, je réalise que cela semble stupide, et toute logique implique que ce ne sera pas le cas, mais j'ai toujours eu 2 choses à l'arrière de la tête qui m'ont rendu incertain.
Quelqu'un, il y a quelques années, m'a dit une fois qu'il le ferait en fait, et cette personne avait une expérience très solide de «connaître leurs affaires».
Le compilateur / framework fait d'autres choses «magiques» en fonction des interfaces que vous implémentez (par exemple: foreach, méthodes d'extension, sérialisation basée sur des attributs, etc.), il est donc logique que cela puisse être aussi «magique».
Bien que j'aie lu beaucoup de choses à ce sujet, et qu'il y ait eu beaucoup de choses implicites, je n'ai jamais été en mesure de trouver un réponse définitive par oui ou par non à cette question.
la source
Je tiens à souligner le point de Brian dans son commentaire, car il est important.
Les finaliseurs ne sont pas des destructeurs déterministes comme en C ++. Comme d'autres l'ont souligné, il n'y a aucune garantie de savoir quand il sera appelé, et même si vous avez suffisamment de mémoire, si jamais il sera appelé.
Mais la mauvaise chose à propos des finaliseurs est que, comme Brian l'a dit, cela fait que votre objet survit à un ramasse-miettes. Cela peut être mauvais. Pourquoi?
Comme vous le savez peut-être ou non, le GC est divisé en générations - Gen 0, 1 et 2, plus le tas d'objets volumineux. Split est un terme vague - vous obtenez un bloc de mémoire, mais il existe des pointeurs indiquant où les objets Gen 0 commencent et se terminent.
Le processus de réflexion est que vous utiliserez probablement beaucoup d'objets qui seront de courte durée. Cela devrait donc être facile et rapide pour le GC d'accéder aux objets de génération 0. Ainsi, lorsqu'il y a une pression de mémoire, la première chose à faire est une collection Gen 0.
Maintenant, si cela ne résout pas suffisamment la pression, il revient en arrière et effectue un balayage Gen 1 (refaire Gen 0), puis si ce n'est toujours pas suffisant, il effectue un balayage Gen 2 (refait Gen 1 et Gen 0). Le nettoyage des objets de longue durée peut donc prendre un certain temps et coûter assez cher (car vos threads peuvent être suspendus pendant l'opération).
Cela signifie que si vous faites quelque chose comme ça:
Votre objet, quoi qu'il arrive, vivra jusqu'à la génération 2. Cela est dû au fait que le GC n'a aucun moyen d'appeler le finaliseur pendant le garbage collection. Ainsi, les objets qui doivent être finalisés sont déplacés vers une file d'attente spéciale pour être nettoyés par un autre thread (le thread de finalisation - qui si vous tuez provoque toutes sortes de mauvaises choses). Cela signifie que vos objets traînent plus longtemps et forcent potentiellement plus de garbage collection.
Donc, tout cela est juste pour ramener à la maison le point que vous souhaitez utiliser IDisposable pour nettoyer les ressources chaque fois que possible et essayer sérieusement de trouver des moyens d'utiliser le finaliseur. C'est dans l'intérêt de votre application.
la source
Il y a déjà beaucoup de bonnes discussions ici, et je suis un peu en retard à la fête, mais je voulais moi-même ajouter quelques points.
C'est la version simple, mais il y a beaucoup de nuances qui peuvent vous tromper sur ce modèle.
À mon avis, il vaut mieux éviter complètement d'avoir des types qui contiennent directement à la fois des références jetables et des ressources natives pouvant nécessiter une finalisation. SafeHandles fournit un moyen très propre de le faire en encapsulant des ressources natives dans des ressources jetables qui fournissent en interne leur propre finalisation (avec un certain nombre d'autres avantages comme la suppression de la fenêtre pendant P / Invoke où un handle natif pourrait être perdu en raison d'une exception asynchrone) .
Définir simplement un SafeHandle rend ce Trivial:
Vous permet de simplifier le type contenant pour:
la source
GC.SuppressFinalize
dans cet exemple. Dans ce contexte, SuppressFinalize ne doit être appelé que s'ilDispose(true)
s'exécute avec succès. SiDispose(true)
échoue à un moment donné après que la finalisation est supprimée mais avant que toutes les ressources (en particulier celles non gérées) ne soient nettoyées, vous voulez toujours que la finalisation se produise afin de faire autant de nettoyage que possible. Mieux vaut déplacer l'GC.SuppressFinalize
appel dans laDispose()
méthode après l'appel àDispose(true)
. Voir les directives de conception du cadre et cet article .Je ne pense pas. Vous avez le contrôle sur le moment où Dispose est appelé, ce qui signifie que vous pouvez en théorie écrire du code d'élimination qui émet des hypothèses sur (par exemple) l'existence d'autres objets. Vous n'avez aucun contrôle sur le moment où le finaliseur est appelé, il serait donc douteux que le finaliseur appelle automatiquement Dispose en votre nom.
EDIT: Je suis parti et j'ai testé, juste pour m'assurer:
la source
Pas dans le cas que vous décrivez, mais le GC appellera le Finalizer pour vous, si vous en avez un.
TOUTEFOIS. Au prochain garbage collection, au lieu d'être collecté, l'objet ira dans la fin de finalisation, tout sera collecté, puis son finaliseur sera appelé. La prochaine collection après cela sera libérée.
En fonction de la pression mémoire de votre application, il se peut que vous n'ayez pas de gc pour cette génération d'objet pendant un certain temps. Ainsi, dans le cas, par exemple, d'un flux de fichiers ou d'une connexion à la base de données, vous devrez peut-être attendre un moment pour que la ressource non gérée soit libérée dans l'appel du finaliseur pendant un certain temps, ce qui causera des problèmes.
la source
Non, ça ne s'appelle pas.
Mais cela rend facile de ne pas oublier de disposer vos objets. Utilisez simplement le
using
mot - clé.J'ai fait le test suivant pour cela:
la source
Le GC n'appellera pas disposer. Il peut appeler votre finaliseur, mais même cela n'est pas garanti dans toutes les circonstances.
Consultez cet article pour une discussion sur la meilleure façon de gérer cela.
la source
La documentation sur IDisposable donne une explication assez claire et détaillée du comportement, ainsi que des exemples de code. Le GC n'appellera PAS la
Dispose()
méthode sur l'interface, mais il appellera le finaliseur pour votre objet.la source
Le modèle IDisposable a été créé principalement pour être appelé par le développeur, si vous avez un objet qui implémente IDispose, le développeur doit soit implémenter le
using
mot clé autour du contexte de l'objet, soit appeler directement la méthode Dispose.La solution de sécurité pour le modèle consiste à implémenter le finaliseur appelant la méthode Dispose (). Si vous ne le faites pas, vous risquez de créer des fuites de mémoire, c'est-à-dire: Si vous créez un wrapper COM et que vous n'appelez jamais System.Runtime.Interop.Marshall.ReleaseComObject (comObject) (qui serait placé dans la méthode Dispose).
Il n'y a pas de magie dans le clr pour appeler automatiquement des méthodes Dispose autre que le suivi des objets qui contiennent des finaliseurs et leur stockage dans la table Finalizer par le GC et leur appel lorsque certaines heuristiques de nettoyage entrent en jeu par le GC.
la source