Pourquoi certaines personnes utilisent-elles la méthode Finalize
par rapport à la méthode Dispose
?
Dans quelles situations utiliseriez-vous la méthode Finalize
par rapport à la méthode Dispose
et vice versa?
D'autres ont déjà couvert la différence entre Dispose
et Finalize
(au fait, la méthode Finalize
est toujours appelée un destructeur dans la spécification du langage), je vais donc ajouter quelques éléments sur les scénarios dans lesquels la méthode Finalize
est pratique.
Certains types encapsulent les ressources disponibles de manière à ce qu’elles soient faciles à utiliser et à éliminer en une seule action. L’usage général est souvent le suivant: ouvrir, lire ou écrire, fermer (Dispose). Cela cadre très bien avec la construction using
.
D'autres sont un peu plus difficiles. WaitEventHandles
pour les instances ne sont pas utilisés comme ceci car ils sont utilisés pour signaler d'un thread à un autre. La question qui se pose alors est de savoir qui devrait appeler Dispose
sur ces derniers? En tant que sauvegarde, les types comme ceux-ci implémentent une méthode Finalize
, qui garantit que les ressources sont supprimées lorsque l'instance n'est plus référencée par l'application.
La méthode de finalisation est appelée lorsque votre objet est collecté et vous n'avez aucune garantie quant à son exécution (vous pouvez le forcer, mais cela affectera les performances).
La méthode Dispose
, quant à elle, doit ê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 à une base de données, descripteurs de fichier, etc.) dès que le code est terminé. avec votre objet.
La pratique standard consiste à implémenter IDisposable
et Dispose
afin que vous puissiez utiliser votre objet dans une déclaration using
. Tels que using(var foo = new MyObject()) { }
. Et dans votre finaliseur, vous appelez Dispose
, juste au cas où le code appelant oubliait de vous en débarrasser.
Finalize est la méthode d'arrière-plan, appelée par le ramasse-miettes lorsqu'elle 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 (descripteurs de fenêtre, connexions de base de données, etc.) lorsqu'elles ne sont plus nécessaires, au lieu de les laisser indéfiniment maintenues jusqu'à ce que le catalogue global atteigne l'objet.
En tant qu'utilisateur d'un objet, vous utilisez toujours Dispose. Finaliser est pour le GC.
En tant qu'implémenteur d'une classe, si vous détenez des ressources gérées qui doivent être éliminées, vous implémentez Dispose. Si vous possédez des ressources natives, vous implémentez à la fois Dispose et Finalize, et les deux appellent une méthode commune qui libère les ressources natives. Ces idiomes sont généralement combinés via une méthode privée Dispose (méthode de suppression), qui élimine les appels avec true et les appels de finalisation avec false. Cette méthode libère toujours les ressources natives, puis vérifie le paramètre de disposition et, le cas échéant, supprime les ressources gérées et appelle GC.SuppressFinalize.
Finaliser
protected
et non public
ou private
afin que la méthode ne puisse pas être appelée directement à partir du code de l'application et qu'elle puisse simultanément appeler la méthode base.Finalize
.Disposer
IDisposable
sur chaque type doté d'un finaliseurDispose
. En d'autres termes, évitez d'utiliser un objet après que la méthode Dispose
a été appelée.Dispose
sur tous les types IDisposable
une fois que vous avez terminé avec euxDispose
à être appelé plusieurs fois sans générer d'erreurs.Dispose
à l'aide de la méthode GC.SuppressFinalize
Dispose
Éliminer/modèle finalisé
Dispose
et Finalize
lorsque vous travaillez avec des ressources non gérées. L'implémentation Finalize
serait exécutée et les ressources seraient toujours libérées lorsque l'objet serait récupéré, même si un développeur avait négligé d'appeler explicitement la méthode Dispose
.Finalize
et Dispose
. En outre, appelez la méthode Dispose
pour tous les objets .NET que vous avez en tant que composants au sein de cette classe (ayant des ressources non gérées en tant que membre) à partir de la méthode Dispose
.Finalize est appelée par le GC lorsque cet objet n'est plus utilisé.
Dispose est simplement 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 implémenté Finalize, alors GC s'assurera de l'appeler.
Voici quelques clés du livre MCSD Certification Toolkit (examen 70-483), page 193:
destructor ≈ (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 ni ressources gérées ni ressources non gérées , elle n’a pas besoin de Implémenter IDisposable ou d’avoir un destructeur.
Si la classe n’a que des ressources gérées , elle doit implémenter IDisposable mais elle n’a pas besoin d’un destructeur. (Lorsque le destructeur s’exécute, vous ne pouvez pas être sûr que les objets gérés existent toujours Existent, vous ne pouvez donc pas appeler leurs méthodes Dispose de toute façon.)
Si la classe n'a que des ressources non gérées , elle doit implémenter IDisposable et nécessite un destructeur .__ au cas où le programme n'appelle pas Dispose.
La méthode Dispose doit pouvoir s'exécuter plusieurs fois en toute sécurité. Vous pouvez y parvenir en utilisant une variable Pour savoir si elle a déjà été exécutée auparavant.
La méthode Dispose doit libérer des 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 peut 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.)
Après avoir libéré des ressources, le destructeur doit appeler GC.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 géré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);
}
// 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;
// We don't need the destructor because
// our resources are already freed.
GC.SuppressFinalize(this);
}
}
}
99% du temps, vous ne devriez pas avoir à vous inquiéter non plus. :) Mais si vos objets contiennent des références à des ressources non gérées (descripteurs de fenêtre, descripteurs de fichier, par exemple), vous devez fournir un moyen à votre objet géré de libérer ces ressources. Finalize donne un contrôle implicite sur le déblocage des ressources. Il est appelé par le ramasse-miettes. Dispose est un moyen de donner un contrôle explicite sur une version de ressources et peut être appelé directement.
Il y a encore beaucoup à apprendre sur le sujet de Garbage Collection , mais c'est un début.
Le finaliseur est destiné au 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/mémoire, etc. ..
Implémenter correctement un finaliseur est notoirement difficile et devrait être évité autant que possible - la classe SafeHandle
(disponible dans .Net v2.0 et versions ultérieures) signifie que vous avez très rarement (si jamais) besoin de mettre en œuvre un finaliseur.
L’interface IDisposable
sert 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 chaque fois qu’ils ont fini d’utiliser un objet.
Notez que si vous avez un finaliseur, vous devez également implémenter l'interface IDisposable
pour permettre aux utilisateurs de libérer explicitement ces ressources plus rapidement que si l'objet avait été nettoyé.
Voir Mise à jour de DG: Elimination, finalisation et gestion des ressources pour ce que je considère comme le meilleur et le plus complet ensemble de recommandations sur les finaliseurs et IDisposable
.
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
}
}
Le résumé est -
De plus, une autre différence est que - dans l'implémentation de Dispose (), vous devez également libérer les ressources gérées, alors que cela ne devrait 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 aient déjà été nettoyées avant d'être finalisées.
Pour une classe qui utilise des ressources non gérées, la meilleure pratique consiste à définir les méthodes (la méthode Dispose () et le finaliseur) à utiliser comme solution de secours au cas où un développeur oublierait 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);
}
Les instances de classe encapsulent souvent le contrôle sur des ressources non gérées par le moteur d'exécution, telles que les descripteurs de fenêtre (HWND), les connexions à une 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 de destructeur en C # et Managed Extensions for C++). Le garbage collector appelle cette méthode à un moment donné après qu'il n'y ait plus de référence valide à l'objet . Dans certains cas, vous pouvez vouloir fournir aux programmeurs utilisant un objet la possibilité de libérer explicitement ces ressources externes avant le garbage collector. libère l'objet. Si une ressource externe est rare ou chère, il est possible d'obtenir de meilleures performances si le programmeur libère explicitement les 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'il utilise 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 par le biais de Dispose, vous devez effectuer un nettoyage implicite à l'aide de la méthode Finalize. Finalize fournit une sauvegarde pour empêcher les ressources de fuir de manière permanente si le programmeur ne parvient pas à appeler Dispose.
Différence entre les méthodes Finalize et Dispose en C #.
Le GC appelle la méthode finalize pour récupérer les ressources non gérées (telles que l’opérateur de fichier, l’API Windows, la connexion réseau, la connexion à la base de données), mais le temps n’est pas fixé à l’appel du GC. Cela s'appelle implicitement par GC, cela signifie que nous n’avons pas de contrôle de bas niveau dessus.
Dispose, méthode: nous avons un contrôle de bas niveau dessus comme nous l'appelons depuis le 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.