Pourquoi certaines personnes utilisent-elles la Finalize
méthode plutôt que la Dispose
méthode?
Dans quelles situations utiliseriez-vous la Finalize
méthode plutôt que la Dispose
méthode et vice versa?
Pourquoi certaines personnes utilisent-elles la Finalize
méthode plutôt que la Dispose
méthode?
Dans quelles situations utiliseriez-vous la Finalize
méthode plutôt que la Dispose
méthode et vice versa?
Réponses:
D'autres ont déjà couvert la différence entre Dispose
et Finalize
(en fait, la Finalize
méthode est toujours appelée destructeur dans la spécification du langage), je vais donc ajouter un peu sur les scénarios où la Finalize
méthode est utile.
Certains types encapsulent les ressources jetables de manière à être faciles à utiliser et à les éliminer en une seule action. L'usage général est souvent le suivant: ouvrir, lire ou écrire, fermer (éliminer). Cela correspond très bien à la using
construction.
D'autres sont un peu plus difficiles. WaitEventHandles
car les instances ne sont pas utilisées comme cela car elles sont utilisées pour signaler d'un thread à un autre. La question devient alors qui devrait faire appel Dispose
à ceux-ci? À titre de sauvegarde, ces types implémentent une Finalize
méthode qui garantit que les ressources sont supprimées lorsque l'instance n'est plus référencée par l'application.
Finalize
peut être justifié est quand il y a un certain nombre d'objets qui sont intéressés à garder une ressource vivante, mais il n'y a aucun moyen par lequel un objet qui cesse d'être intéressé par la ressource peut savoir si c'est le le dernier. Dans ce cas, Finalize
ne se déclenche généralement que lorsque personne ne s'intéresse à l'objet. Le timing lâche de Finalize
est horrible pour les ressources non fongibles telles que les fichiers et les verrous, mais peut convenir pour les ressources fongibles.
La méthode du finaliseur est appelée lorsque votre objet est récupéré et vous n'avez aucune garantie quand cela se produira (vous pouvez le forcer, mais cela nuira aux performances).
La Dispose
méthode, d'autre part, est censée être appelée par le code qui a créé votre classe afin que vous puissiez nettoyer et libérer toutes les ressources que vous avez acquises (données non gérées, connexions à la base de données, descripteurs de fichiers, etc.) au moment où le code est terminé. votre objet.
La pratique standard consiste à implémenter IDisposable
et à Dispose
utiliser votre objet dans un using
état. Tels que using(var foo = new MyObject()) { }
. Et dans votre finaliseur, vous appelez Dispose
, juste au cas où le code appelant aurait oublié de vous éliminer.
Finalize est la méthode de backstop, appelée par le garbage collector lorsqu'il récupère un objet. Dispose est la méthode de «nettoyage déterministe», appelée par les applications pour libérer des ressources natives précieuses (poignées de fenêtre, connexions à la base de données, etc.) lorsqu'elles ne sont plus nécessaires, plutôt que de les laisser indéfiniment jusqu'à ce que le GC arrive à l'objet.
En tant qu'utilisateur d'un objet, vous utilisez toujours Dispose. Finalize est pour le GC.
En tant qu'implémenteur d'une classe, si vous détenez des ressources gérées qui doivent être supprimées, vous implémentez Dispose. Si vous détenez des ressources natives, vous implémentez à la fois Dispose et Finalize et appelez toutes les deux une méthode commune qui libère les ressources natives. Ces idiomes sont généralement combinés via une méthode Dispose privée (élimination booléenne), qui Dispose les appels avec true et Finalise les appels avec false. Cette méthode libère toujours les ressources natives, puis vérifie le paramètre de suppression, et si elle est vraie, elle supprime les ressources gérées et appelle GC.SuppressFinalize.
Dispose
c'est bien, et l'implémenter correctement est généralement facile. Finalize
est mauvais, et sa mise en œuvre correcte est généralement difficile. Entre autres choses, parce que le GC s'assurera qu'aucune identité d'objet ne sera jamais "recyclée" tant qu'il y aura une référence à cet objet, il est facile de nettoyer un tas d' Disposable
objets, dont certains peuvent avoir déjà été nettoyés, est aucun problème; toute référence à un objet sur lequel Dispose
a déjà été appelé restera une référence à un objet sur lequel Dispose
a déjà été appelé.
Fred
possède le descripteur de fichier # 42 et le ferme, le système peut attacher ce même numéro à certains descripteurs de fichiers qui sont attribués à une autre entité. Dans ce cas, le descripteur de fichier # 42 ne ferait pas référence au fichier fermé de Fred, mais au fichier qui était en cours d'utilisation par cette autre entité; car Fred
essayer de refermer la poignée # 42 serait désastreux. Il est possible d'essayer de suivre de manière fiable à 100% si un objet non géré a encore été publié. Essayer de garder une trace de plusieurs objets est beaucoup plus difficile.
Finaliser
protected
, pas public
ou private
pour que la méthode ne puisse pas être appelée directement à partir du code de l'application et en même temps, elle peut appeler la base.Finalize
méthodeDisposer
IDisposable
sur chaque type qui a un finaliseurDispose
méthode. En d'autres termes, évitez d'utiliser un objet après avoir Dispose
appelé la méthode.Dispose
tous les IDisposable
types une fois que vous en avez fini avec euxDispose
d'être appelé plusieurs fois sans générer d'erreurs.Dispose
méthode à l'aide de la GC.SuppressFinalize
méthodeDispose
méthodes internesDispose / Finalized Pattern
Dispose
et Finalize
lorsque vous travaillez avec des ressources non gérées. L' Finalize
implémentation s'exécuterait et les ressources seraient toujours libérées lorsque l'objet est récupéré, même si un développeur négligeait d'appeler la Dispose
méthode explicitement.Finalize
méthode ainsi que la Dispose
méthode. Appelez en outre la Dispose
méthode pour tous les objets .NET que vous avez en tant que composants dans cette classe (ayant des ressources non managées comme membre) à partir de la Dispose
méthode.Finalize est appelé par le GC lorsque cet objet n'est plus utilisé.
Dispose n'est qu'une méthode normale que l'utilisateur de cette classe peut appeler pour libérer toutes les ressources.
Si l'utilisateur a oublié d'appeler Dispose et si la classe a Finalize implémenté, GC s'assurera qu'il sera appelé.
Il y a quelques clés dans le livre MCSD Certification Toolkit (examen 70-483) page 193:
destructeur ≈ (il est presque égal à)base.Finalize()
, le destructeur est converti en une version de substitution de la méthode Finalize qui exécute le code du destructeur puis appelle la méthode Finalize de la classe de base. Ensuite, c'est totalement non déterministe, vous ne pouvez pas savoir quand sera appelé car cela dépend de GC.
Si une classe ne contient aucune ressource gérée et aucune ressource non gérée , elle ne doit pas implémenter IDisposable
ni avoir de destructeur.
Si la classe n'a que des ressources gérées , elle doit implémenter IDisposable
mais ne doit pas avoir de destructeur. (Lorsque le destructeur s'exécute, vous ne pouvez pas être sûr que les objets gérés existent toujours, vous ne pouvez donc pas appeler leurs Dispose()
méthodes de toute façon.)
Si la classe n'a que des ressources non gérées , elle doit implémenter IDisposable
et a besoin d'un destructeur au cas où le programme n'appelle pas Dispose()
.
Dispose()
La méthode doit pouvoir être exécutée plusieurs fois en toute sécurité. Vous pouvez y parvenir en utilisant une variable pour savoir si elle a été exécutée auparavant.
Dispose()
devrait libérer les ressources gérées et non gérées .
Le destructeur ne doit libérer que des ressources non gérées . Lorsque le destructeur s'exécute, vous ne pouvez pas être sûr que les objets gérés existent toujours, vous ne pouvez donc pas appeler leurs méthodes Dispose de toute façon. Ceci est obtenu en utilisant le protected void Dispose(bool disposing)
modèle canonique , où seules les ressources gérées sont libérées (supprimées) quand disposing == true
.
Après avoir libéré des ressources, Dispose()
doit appelerGC.SuppressFinalize
, afin que l'objet puisse ignorer la file d'attente de finalisation.
Un exemple d'implémentation pour une classe avec des ressources non managées et gérées:
using System;
class DisposableClass : IDisposable
{
// A name to keep track of the object.
public string Name = "";
// Free managed and unmanaged resources.
public void Dispose()
{
FreeResources(true);
// We don't need the destructor because
// our resources are already freed.
GC.SuppressFinalize(this);
}
// Destructor to clean up unmanaged resources
// but not managed resources.
~DisposableClass()
{
FreeResources(false);
}
// Keep track if whether resources are already freed.
private bool ResourcesAreFreed = false;
// Free resources.
private void FreeResources(bool freeManagedResources)
{
Console.WriteLine(Name + ": FreeResources");
if (!ResourcesAreFreed)
{
// Dispose of managed resources if appropriate.
if (freeManagedResources)
{
// Dispose of managed resources here.
Console.WriteLine(Name + ": Dispose of managed resources");
}
// Dispose of unmanaged resources here.
Console.WriteLine(Name + ": Dispose of unmanaged resources");
// Remember that we have disposed of resources.
ResourcesAreFreed = true;
}
}
}
99% du temps, vous ne devriez pas vous inquiéter non plus. :) Mais, si vos objets contiennent des références à des ressources non gérées (poignées de fenêtre, poignées de fichier, par exemple), vous devez fournir un moyen pour votre objet géré de libérer ces ressources. Finalize donne un contrôle implicite sur la libération des ressources. Il est appelé par le garbage collector. Dispose est un moyen de donner un contrôle explicite sur une version des ressources et peut être appelé directement.
Il y a beaucoup plus à apprendre sur le sujet de la collecte des ordures , mais c'est un début.
Le finaliseur est pour le nettoyage implicite - vous devez l'utiliser chaque fois qu'une classe gère des ressources qui doivent absolument être nettoyées, sinon vous perdriez des poignées / de la mémoire, etc.
La mise en œuvre correcte d'un finaliseur est notoirement difficile et doit être évitée autant que possible - le SafeHandle
classe (disponible dans .Net v2.0 et supérieur) signifie maintenant que vous avez très rarement (voire jamais) besoin d'implémenter un finaliseur.
le IDisposable
interface est destinée au nettoyage explicite et est beaucoup plus couramment utilisée - vous devez l'utiliser pour permettre aux utilisateurs de libérer ou de nettoyer explicitement les ressources lorsqu'ils ont fini d'utiliser un objet.
Notez que si vous avez un finaliseur, vous devez également implémenter l' IDisposable
interface pour permettre aux utilisateurs de libérer explicitement ces ressources plus tôt qu'ils ne le seraient si l'objet était récupéré.
Voir DG Update: Dispose, Finalization, and Resource Management pour ce que je considère être le meilleur et le plus complet des recommandations sur les finaliseurs et IDisposable
.
Le résumé est -
En outre, une autre différence est que dans l'implémentation Dispose (), vous devez également libérer les ressources gérées , alors que cela ne doit pas être fait dans le Finalizer. En effet, il est très probable que les ressources gérées référencées par l'objet ont déjà été nettoyées avant qu'il ne soit prêt à être finalisé.
Pour une classe qui utilise des ressources non managées, la meilleure pratique consiste à définir à la fois - la méthode Dispose () et le Finalizer - à utiliser comme solution de secours au cas où un développeur oublie de supprimer explicitement l'objet. Les deux peuvent utiliser une méthode partagée pour nettoyer les ressources gérées et non gérées: -
class ClassWithDisposeAndFinalize : IDisposable
{
// Used to determine if Dispose() has already been called, so that the finalizer
// knows if it needs to clean up unmanaged resources.
private bool disposed = false;
public void Dispose()
{
// Call our shared helper method.
// Specifying "true" signifies that the object user triggered the cleanup.
CleanUp(true);
// Now suppress finalization to make sure that the Finalize method
// doesn't attempt to clean up unmanaged resources.
GC.SuppressFinalize(this);
}
private void CleanUp(bool disposing)
{
// Be sure we have not already been disposed!
if (!this.disposed)
{
// If disposing equals true i.e. if disposed explicitly, dispose all
// managed resources.
if (disposing)
{
// Dispose managed resources.
}
// Clean up unmanaged resources here.
}
disposed = true;
}
// the below is called the destructor or Finalizer
~ClassWithDisposeAndFinalize()
{
// Call our shared helper method.
// Specifying "false" signifies that the GC triggered the cleanup.
CleanUp(false);
}
Le meilleur exemple que je connaisse.
public abstract class DisposableType: IDisposable
{
bool disposed = false;
~DisposableType()
{
if (!disposed)
{
disposed = true;
Dispose(false);
}
}
public void Dispose()
{
if (!disposed)
{
disposed = true;
Dispose(true);
GC.SuppressFinalize(this);
}
}
public void Close()
{
Dispose();
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
// managed objects
}
// unmanaged objects and resources
}
}
Différence entre les méthodes Finalize et Dispose en C #.
GC appelle la méthode finalize pour récupérer les ressources non gérées (telles que l'opérarion de fichier, l'api Windows, la connexion réseau, la connexion à la base de données) mais l'heure n'est pas fixe quand GC l'appellerait. Il est appelé implicitement par GC, cela signifie que nous n'avons pas de contrôle de bas niveau dessus.
Méthode Dispose: nous avons un contrôle de bas niveau dessus comme nous l'appelons à partir du code. nous pouvons récupérer les ressources non gérées chaque fois que nous estimons qu'elles ne sont pas utilisables. Nous pouvons y parvenir en implémentant le modèle IDisposal.
Les instances de classe encapsulent souvent le contrôle des ressources qui ne sont pas gérées par le runtime, telles que les descripteurs de fenêtre (HWND), les connexions à la base de données, etc. Par conséquent, vous devez fournir à la fois un moyen explicite et implicite de libérer ces ressources. Fournissez un contrôle implicite en implémentant la méthode Finalize protégée sur un objet (syntaxe du destructeur en C # et les extensions gérées pour C ++). Le garbage collector appelle cette méthode à un moment donné après qu'il n'y a plus de références valides à l'objet. Dans certains cas, vous souhaiterez peut-être fournir aux programmeurs utilisant un objet la possibilité de libérer explicitement ces ressources externes avant que le garbage collector ne libère l'objet. Si une ressource externe est rare ou coûteuse, de meilleures performances peuvent être obtenues si le programmeur libère explicitement des ressources lorsqu'elles ne sont plus utilisées. Pour fournir un contrôle explicite, implémentez la méthode Dispose fournie par l'interface IDisposable. Le consommateur de l'objet doit appeler cette méthode lorsqu'elle a terminé d'utiliser l'objet. Dispose peut être appelé même si d'autres références à l'objet sont actives.
Notez que même lorsque vous fournissez un contrôle explicite via Dispose, vous devez fournir un nettoyage implicite à l'aide de la méthode Finalize. Finalize fournit une sauvegarde pour empêcher les ressources de fuir définitivement si le programmeur ne parvient pas à appeler Dispose.
La principale différence entre Dispose et Finalize est que:
Dispose
est généralement appelé par votre code. Les ressources sont libérées instantanément lorsque vous l'appelez. Les gens oublient d'appeler la méthode, donc la using() {}
déclaration est inventée. Lorsque votre programme {}
aura terminé l'exécution du code à l'intérieur de , il appellera la Dispose
méthode automatiquement.
Finalize
n'est pas appelé par votre code. Il est censé être appelé par le garbage collector (GC). Cela signifie que la ressource peut être libérée à tout moment à l'avenir chaque fois que le GC décide de le faire. Lorsque GC fait son travail, il passe par de nombreuses méthodes Finalize. Si vous avez une logique lourde, cela ralentira le processus. Cela peut entraîner des problèmes de performances pour votre programme. Faites donc attention à ce que vous y mettez.
Personnellement, j'écrirais la majeure partie de la logique de destruction dans Dispose. Espérons que cela dissipe la confusion.
Comme nous le savons, l'élimination et la finalisation sont utilisées pour libérer les ressources non gérées .. mais la différence est que la finalisation utilise deux cycles pour libérer les ressources, alors que l'élimination utilise un cycle ..
Pour répondre à la première partie, vous devez fournir des exemples où les gens utilisent une approche différente pour exactement le même objet de classe. Sinon, il est difficile (voire étrange) de répondre.
Quant à la deuxième question, il vaut mieux lire d'abord cette utilisation correcte de l'interface IDisposable qui prétend que
C'est ton choix! Mais choisissez Dispose.
En d'autres termes: le GC ne connaît que le finaliseur (le cas échéant. Aussi appelé destructeur pour Microsoft). Un bon code tentera de nettoyer les deux (finalizer et Dispose).