2009-09-10 12 views
6

Je lisais l'article this l'autre jour et je me demandais pourquoi il y avait un Finalizer avec la méthode Dispose. J'ai lu here sur SO pour savoir pourquoi vous pourriez vouloir ajouter Dispose au Finalizer. Ma curiosité est de savoir quand le Finalizer sera appelé par-dessus la méthode Dispose elle-même? Existe-t-il un exemple de code ou est-ce basé sur quelque chose qui se passe sur le système que le logiciel exécute? Si c'est le cas, qu'est-ce qui pourrait arriver à ce que la méthode Dispose ne soit pas exécutée par le GC?Quand est-ce que la méthode d'élimination ne serait pas appelée?

Répondre

9

Le but du finaliser ici est simplement une mesure de sécurité contre les fuites de mémoire (si vous vous trouvez pas pour appeler explicitement Dispose). Cela signifie également que vous n'avez pas à disposer de vos objets si vous voulez qu'ils libèrent des ressources lorsque le programme s'arrête, car le GC sera forcé de finaliser et de collecter tous les objets de toute façon.

Par ailleurs, il est important de disposer l'objet légèrement différemment à partir du finaliseur.

~MyClass() 
{ 
    Dispose(false); 
} 

public void Dispose() 
{ 
    Dispose(true); 
    GC.SuppressFinalize(this); 
} 

protected void Dispose(disposing) 
{ 
    if (!this.disposed) 
    { 
     if (disposing) 
     { 
      // Dispose managed resources here. 
     } 
     // Dispose unmanaged resources here. 
    } 
    this.disposed = true; 
} 

La raison pour laquelle vous ne veulent disposer des ressources gérées dans votre Finaliser est que vous REELLEMENT créer de solides références à eux en faisant, et cela pourrait empêcher le GC de le faire est le travail correctement et la collecte leur. Les ressources non managées (par exemple les poignées Win32 et autres) doivent toujours être explicitement fermées/éliminées, bien sûr, puisque le CLR n'en a aucune connaissance.

+1

Et une autre raison de ne pas disposer des ressources gérées dans votre finaliseur ... Il est possible qu'elles aient déjà été GC'd au moment de l'exécution de votre finaliseur. Essayer de les éliminer quand ils ont déjà été collectés provoquerait une erreur d'exécution. – LukeH

+1

@Luke: C'est vrai, mais cela peut être évité assez facilement en mettant toutes vos références à zéro et ensuite faire une vérification nulle avant de disposer. – Noldorin

+0

@Noldorin - Où la désinscription des événements tomberait-elle dans votre exemple? Je comprends techniquement qu'ils tomberaient sous la gestion, mais si nous avions un objet lié à cette classe à travers un événement et nous ne l'annulons pas dans la partie non managée (en supposant que l'utilisateur n'appelle pas directement Dispose et sa gauche au GC pour le nettoyer en haut). Serait-il sûr/correct de mettre un événement dans la section gérée pour vous assurer que cela se produit? L'effet secondaire pourrait être que quelqu'un pense qu'il dispose d'un objet, mais en réalité, il n'est jamais éliminé à cause du lien d'événement entre cette classe et l'autre. – SwDevMan81

4

Ceci est principalement là pour vous protéger. Vous ne pouvez pas dicter ce que fera l'utilisateur final de votre classe. En fournissant un finaliseur en plus d'une méthode Dispose, le GC va "Disposer" de votre objet, libérant vos ressources de manière appropriée, même si l'utilisateur oublie d'appeler Dispose() ou utilise mal votre classe.

+1

Il est à noter que le GC est non-déterministe, donc il n'y a aucune garantie de quand, * ou même *, votre finaliseur sera appelé. – LukeH

+0

Oui - Si votre programme fonctionne assez longtemps, votre objet est le plus susceptible d'être finalisé. De plus, s'il se ferme proprement, il sera finalisé. Mais il n'y a aucune garantie avec le GC - ce qui explique pourquoi IDisposable existe en premier lieu. –

1

La méthode dispose doit être explicitement appelée, soit en appelant Dispose() ou en ayant l'objet dans une instruction using. Le GC appellera toujours le finaliseur, donc s'il y a quelque chose qui doit arriver avant que les objets soient éliminés, le finaliseur devrait au moins vérifier pour s'assurer que tout dans l'objet est nettoyé. Vous voulez éviter de nettoyer les objets dans le finaliseur, dans la mesure du possible, car cela nécessite un travail supplémentaire comparé à la disposition préalable (comme l'appel), mais vous devez toujours au moins vérifier le finaliseur s'il y a des objets traînant autour de ce besoin d'être enlevé.

2

Le Finalizer est appelé lorsque l'objet est collecté. Dispose doit être appelé explicitement. Dans le code suivant, le finaliseur sera appelé mais la méthode Dispose ne l'est pas.

class Foo : IDisposable 
{ 
    public void Dispose() 
    { 
    Console.WriteLine("Disposed"); 
    } 

    ~Foo() 
    { 
    Console.WriteLine("Finalized"); 
    } 
} 

... 

public void Go() 
{ 
    Foo foo = new Foo(); 
} 
+1

Ce n'est pas tout à fait vrai. Le finaliseur est appelé un certain temps après que l'objet serait autrement éligible pour la récupération de place (c'est-à-dire que l'application ne fait plus référence à l'instance). Cependant, comme le finaliseur doit être exécuté pour l'instance, le CLR enracine l'objet et, par conséquent, il n'est pas collecté avant que le finaliseur ne soit exécuté. –

+0

Il n'y a également aucune garantie qu'un objet sera * jamais * GC'd ou que son finalizer sera * jamais * appelé. C'est pourquoi il est doublement important de s'assurer que vous disposez des objets 'IDisposable' correctement. – LukeH

0

Une note subtile, mais importante pas encore mentionné: un but de Dispose rarement considéré est d'empêcher un objet d'être nettoyé prématurément. Les objets avec les finaliseurs doivent être écrits avec soin, sinon un finaliseur plus tôt que prévu. Un finalizer ne peut pas s'exécuter avant le début du dernier appel de méthode qui sera effectué sur un objet (*), mais il peut parfois exécuter pendant le dernier appel de méthode si l'objet est abandonné une fois la méthode terminée. Le code qui détruit correctement un objet ne peut pas abandonner l'objet avant d'appeler Dispose, il n'y a donc aucun risque qu'un finaliseur fasse des ravages sur du code qui utilise correctement Dispose. D'autre part, si la dernière méthode d'utilisation d'un objet utilise des entités qui seront nettoyées dans le finaliseur après sa dernière utilisation de la référence d'objet elle-même, il est possible que garbage-collector appelle Finalize sur l'objet et nettoie des entités qui sont encore utilisées.Le remède consiste à s'assurer que toute méthode d'appel qui utilise des entités qui vont être nettoyées par un finaliseur doit être suivie à un certain moment par un appel de méthode qui utilise "this". GC.KeepAlive (this) est une bonne méthode à utiliser pour cela. (*) Les méthodes non virtuelles qui sont étendues au code en ligne qui ne fait rien avec l'objet peuvent être exemptées de cette règle, mais Dispose est généralement une méthode virtuelle.