L'intérêt de Dispose est pour libérer les ressources non gérées. Il faut le faire à un moment donné, sinon elles ne seront jamais nettoyées. Le ramasseur d'ordures ne sait pas comment d'appeler DeleteHandle()
sur une variable de type IntPtr
Il ne sait pas. si ou non, il doit appeler DeleteHandle()
.
Note : Qu'est-ce qu'un ressource non gérée ? Si vous l'avez trouvé dans le Microsoft .NET Framework : il est géré. Si vous êtes allé fouiller vous-même dans MSDN, c'est non géré. Tout ce que vous avez utilisé comme appels P/Invoke pour sortir du monde confortable de tout ce qui est disponible dans le .NET Framework n'est pas géré - et vous êtes maintenant responsable de son nettoyage.
L'objet que vous avez créé doit exposer algunos que le monde extérieur peut appeler, afin de nettoyer les ressources non gérées. La méthode peut être nommée comme vous le souhaitez :
public void Cleanup()
ou
public void Shutdown()
Mais au lieu de cela, il existe un nom standardisé pour cette méthode :
public void Dispose()
Une interface a même été créée, IDisposable
qui n'a que cette seule méthode :
public interface IDisposable
{
void Dispose()
}
Donc vous faites en sorte que votre objet expose le IDisposable
et de cette façon vous promettez que vous avez écrit cette unique méthode pour nettoyer vos ressources non gérées :
public void Dispose()
{
Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle);
}
Et vous avez terminé. Sauf que vous pouvez faire mieux.
Que faire si votre objet a alloué un 250MB System.Drawing.Bitmap (c'est-à-dire la classe Bitmap gérée par .NET) comme une sorte de tampon d'image ? Bien sûr, il s'agit d'un objet .NET géré, et le ramasseur d'ordures le libérera. Mais voulez-vous vraiment laisser 250 Mo de mémoire en suspens, en attendant que le ramasseur d'ordures éventuellement arrive et le libère ? Et s'il y a un ouvrir une connexion à la base de données ? Nous ne voulons certainement pas que cette connexion reste ouverte, en attendant que le GC finalise l'objet.
Si l'utilisateur a appelé Dispose()
(ce qui signifie qu'ils n'ont plus l'intention d'utiliser l'objet) ; pourquoi ne pas se débarrasser de ces bitmaps et de ces connexions de base de données inutiles ?
Alors maintenant, nous le ferons :
- se débarrasser des ressources non gérées (parce que nous le devons), et
- se débarrasser des ressources gérées (parce que nous voulons être utiles)
Donc, mettons à jour notre Dispose()
pour se débarrasser de ces objets gérés :
public void Dispose()
{
//Free unmanaged resources
Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle);
//Free managed resources too
if (this.databaseConnection != null)
{
this.databaseConnection.Dispose();
this.databaseConnection = null;
}
if (this.frameBufferImage != null)
{
this.frameBufferImage.Dispose();
this.frameBufferImage = null;
}
}
Et tout va bien, sauf que vous pouvez faire mieux !
Et si la personne a oublié d'appeler Dispose()
sur votre objet ? Alors il y aurait des fuites non géré ressources !
Note : Ils ne fuient pas géré car le ramasseur d'ordures va finir par s'exécuter, sur un fil d'arrière-plan, et libérer la mémoire associée à tous les objets inutilisés. Cela comprendra votre objet, ainsi que tous les objets gérés que vous utilisez (par exemple, l'objet Bitmap
et le DbConnection
).
Si la personne a oublié d'appeler Dispose()
nous pouvons toujours sauver leur bacon ! Nous avons encore un moyen de l'appeler pour elles : lorsque le ramasseur d'ordures arrive enfin à libérer (c'est-à-dire à finaliser) notre objet.
Note : Le ramasseur d'ordures finira par libérer tous les objets gérés. Lorsqu'il le fait, il appelle la fonction Finalize
sur l'objet. Le GC ne sait pas, ou ne ne se soucie pas de su Éliminer méthode. C'est juste un nom que nous avons choisi pour une méthode que nous appelons quand nous voulons nous se débarrasser de trucs non gérés.
La destruction de notre objet par le collecteur d'ordures est l'étape suivante. parfait Il est temps de libérer ces satanées ressources non gérées. Pour ce faire, nous surchargeons la fonction Finalize()
méthode.
Note : En C#, vous ne surchargez pas explicitement la fonction Finalize()
méthode. Vous écrivez une méthode qui ressemble à a Destructeur C++ et le le compilateur considère qu'il s'agit de votre implémentation de la fonction Finalize()
méthode :
~MyObject()
{
//we're being finalized (i.e. destroyed), call Dispose in case the user forgot to
Dispose(); //<--Warning: subtle bug! Keep reading!
}
Mais il y a un bug dans ce code. Vous voyez, le ramasseur de déchets fonctionne sur une base fil de fond ; vous ne connaissez pas l'ordre dans lequel deux objets sont détruits. Il est tout à fait possible que dans votre Dispose()
le code géré L'objet dont vous essayez de vous débarrasser (parce que vous vouliez être utile) n'est plus là :
public void Dispose()
{
//Free unmanaged resources
Win32.DestroyHandle(this.gdiCursorBitmapStreamFileHandle);
//Free managed resources too
if (this.databaseConnection != null)
{
this.databaseConnection.Dispose(); //<-- crash, GC already destroyed it
this.databaseConnection = null;
}
if (this.frameBufferImage != null)
{
this.frameBufferImage.Dispose(); //<-- crash, GC already destroyed it
this.frameBufferImage = null;
}
}
Donc ce dont vous avez besoin est un moyen pour Finalize()
de dire Dispose()
qu'il devrait ne pas toucher à la gestion ressources (parce qu'elles pourrait ne pas être là plus), tout en libérant des ressources non gérées.
Le modèle standard pour faire cela est d'avoir Finalize()
y Dispose()
appellent tous deux un troisième ( !); où vous passez un booléen disant si vous l'appelez de Dispose()
(par opposition à Finalize()
), ce qui signifie qu'il est possible de libérer les ressources gérées.
Ce site interne méthode pourrait peut recevoir un nom arbitraire comme "CoreDispose" ou "MyInternalDispose", mais la tradition veut qu'on l'appelle Dispose(Boolean)
:
protected void Dispose(Boolean disposing)
Mais un nom de paramètre plus utile pourrait être :
protected void Dispose(Boolean itIsSafeToAlsoFreeManagedObjects)
{
//Free unmanaged resources
Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle);
//Free managed resources too, but only if I'm being called from Dispose
//(If I'm being called from Finalize then the objects might not exist
//anymore
if (itIsSafeToAlsoFreeManagedObjects)
{
if (this.databaseConnection != null)
{
this.databaseConnection.Dispose();
this.databaseConnection = null;
}
if (this.frameBufferImage != null)
{
this.frameBufferImage.Dispose();
this.frameBufferImage = null;
}
}
}
Et vous changez votre implémentation de la IDisposable.Dispose()
méthode à :
public void Dispose()
{
Dispose(true); //I am calling you from Dispose, it's safe
}
et votre finisseur à :
~MyObject()
{
Dispose(false); //I am *not* calling you from Dispose, it's *not* safe
}
Note : Si votre objet descend d'un objet qui implémente Dispose
alors n'oubliez pas d'appeler leur base Dispose lorsque vous remplacez la méthode Dispose :
public override void Dispose()
{
try
{
Dispose(true); //true: safe to free managed resources
}
finally
{
base.Dispose();
}
}
Et tout va bien, sauf que vous pouvez faire mieux !
Si l'utilisateur appelle Dispose()
sur votre objet, alors tout a été nettoyé. Plus tard, lorsque le ramasseur d'ordures arrivera et appellera Finalize, il appellera alors Dispose
encore.
Non seulement c'est un gaspillage, mais si votre objet contient des références inutiles à des objets dont vous vous êtes déjà débarrassé à partir de la fonction dernier Appel à Dispose()
vous allez essayer de les jeter à nouveau !
Vous remarquerez que dans mon code, j'ai pris soin de supprimer les références aux objets que j'ai éliminés, de sorte que je n'essaie pas d'appeler Dispose
sur une référence d'objet poubelle. Mais cela n'a pas empêché un bug subtil de se glisser.
Lorsque l'utilisateur appelle Dispose()
: la poignée CursorFileBitmapIconServiceHandle est détruit. Plus tard, lorsque le ramasseur d'ordures s'exécutera, il tentera de détruire à nouveau le même identifiant.
protected void Dispose(Boolean iAmBeingCalledFromDisposeAndNotFinalize)
{
//Free unmanaged resources
Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle); //<--double destroy
...
}
La façon de résoudre ce problème est de dire au ramasseur de déchets qu'il n'a pas besoin de finaliser l'objet - ses ressources ont déjà été nettoyées et il n'y a plus rien à faire. Vous faites cela en appelant GC.SuppressFinalize()
dans le Dispose()
méthode :
public void Dispose()
{
Dispose(true); //I am calling you from Dispose, it's safe
GC.SuppressFinalize(this); //Hey, GC: don't bother calling finalize later
}
Maintenant que l'utilisateur a appelé Dispose()
nous avons :
- ressources libérées non gérées
- ressources gérées libérées
Il est inutile que le GC exécute le finisseur - tout est réglé.
Ne pourrais-je pas utiliser Finalize pour nettoyer les ressources non gérées ?
La documentation pour Object.Finalize
dit :
La méthode Finalize est utilisée pour effectuer des opérations de nettoyage sur les ressources non gérées détenues par l'objet courant avant la destruction de l'objet.
Mais la documentation MSDN dit aussi, pour IDisposable.Dispose
:
Exécute les tâches définies par l'application associées à la libération, au déblocage ou à la réinitialisation des ressources non gérées.
Alors, c'est lequel ? Quel est l'endroit où je dois nettoyer les ressources non gérées ? La réponse est :
C'est votre choix ! Mais choisissez Dispose
.
Vous pouvez certainement placer votre nettoyage non géré dans le finaliseur :
~MyObject()
{
//Free unmanaged resources
Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle);
//A C# destructor automatically calls the destructor of its base class.
}
Le problème est que vous n'avez aucune idée du moment où le ramasseur de déchets finalisera votre objet. Vos ressources natives non gérées, non nécessaires, non utilisées resteront en place jusqu'à ce que le ramasseur d'ordures éventuellement fonctionne. Ensuite, il appellera votre méthode de finalisation pour nettoyer les ressources non gérées. La documentation de Object.Finalize souligne ce point :
Le moment exact où le finaliseur s'exécute n'est pas défini. Pour garantir la libération déterministe des ressources pour les instances de votre classe, implémentez une fonction de type Fermer ou fournir une IDisposable.Dispose
mise en œuvre.
C'est la vertu de l'utilisation de Dispose
pour nettoyer les ressources non gérées ; vous pouvez savoir, et contrôler, quand les ressources non gérées sont nettoyées. Leur destruction est "déterministe" .
Pour répondre à votre question initiale : Pourquoi ne pas libérer la mémoire maintenant, plutôt que pour le moment où la GC décide de le faire ? J'ai un logiciel de reconnaissance faciale qui besoins pour se débarrasser de 530 Mo d'images internes maintenant puisqu'ils ne sont plus nécessaires. Si nous ne le faisons pas, la machine s'arrête de tourner.
Lecture en prime
Pour tous ceux qui aiment le style de cette réponse (expliquer la por qué donc le comment devient évident), je vous suggère de lire le premier chapitre de l'ouvrage Essential COM de Don Box :
En 35 pages, il explique les problèmes liés à l'utilisation d'objets binaires et invente COM sous vos yeux. Une fois que vous aurez réalisé le por qué de COM, les 300 pages restantes sont évidentes, et ne font que détailler la mise en œuvre de Microsoft.
Je pense que tout programmeur qui a déjà eu affaire à des objets ou à des COM devrait, au minimum, lire le premier chapitre. C'est la meilleure explication de tout ce qui existe.
Lecture supplémentaire
Quand tout ce que vous savez est faux archives par Eric Lippert
Il est donc très difficile d'écrire un finisseur correct, et le meilleur conseil que je puisse te donner est de ne pas essayer. .
38 votes
J'aime la réponse acceptée parce qu'elle vous indique le "modèle" correct d'utilisation d'IDisposable, mais comme l'OP l'a dit dans son édition, elle ne répond pas à sa question. IDisposable n'appelle pas la GC, il ne fait que marquer un objet comme pouvant être détruit. Mais quel est le vrai moyen de libérer de la mémoire "tout de suite" au lieu d'attendre que la GC intervienne ? Je pense que cette question mérite une discussion plus approfondie.
47 votes
IDisposable
ne marque rien. Le siteDispose
fait ce qu'elle doit faire pour nettoyer les ressources utilisées par l'instance. Cela n'a rien à voir avec la GC.0 votes
@John - Je pense que vous avez mal compris ma question. Supposons que ma classe implémente IDisposable et que dans ma classe, j'utilise une très grande ArrayList ou un autre objet géré que je marque comme nul lorsque Dispose(true) est appelé sur une instance de ma classe. Maintenant, selon le modèle, ce que je fais ici est de marquer la référence ArrayList comme nulle, mais les données des objets ArrayList seront toujours en mémoire jusqu'à ce que le GC entre en jeu, ce qui est non déterministe. La question est donc de savoir comment libérer ce bloc de mémoire non référencé immédiatement et de manière déterministe.
0 votes
@desigeek : encore une fois, vous ne comprenez pas bien.
IDisposable
. Cela n'a rien à voir avec la libération de la mémoire. De plus, vous ne devriez pas mettre votre référence à null du tout. Laissez-la tranquille et laissez la GC faire son travail.4 votes
@John. Je comprends
IDisposable
. Et c'est pourquoi j'ai dit que la réponse acceptée ne répond pas à la question du PO (et à l'édition de suivi), à savoir si IDisposable aidera à <i>libérer la mémoire</i>. PuisqueIDisposable
n'a rien à voir avec la libération de la mémoire, seulement des ressources, alors comme vous l'avez dit, il n'y a pas besoin de mettre les références gérées à null du tout, ce qui est ce que le PO faisait dans son exemple. Donc, la réponse correcte à sa question est "Non, cela n'aide pas à libérer la mémoire plus rapidement. En fait, cela n'aide pas du tout à libérer la mémoire, seulement les ressources". Mais quoi qu'il en soit, merci pour votre contribution.10 votes
@desigeek : si c'est le cas, alors vous n'auriez pas dû dire "IDisposable n'appelle pas le GC, il marque juste un objet comme étant destructible".
5 votes
@desigeek : Il n'y a pas de moyen garanti de libérer la mémoire de manière déterministe. Vous pouvez appeler GC.Collect(), mais c'est une demande polie, pas une demande. Tous les threads en cours d'exécution doivent être suspendus pour que le ramassage des ordures puisse avoir lieu - lisez le concept des points de sécurité .NET si vous voulez en savoir plus, par ex. msdn.microsoft.com/fr/us/library/678ysw69(v=vs.110).aspx . Si un thread ne peut pas être suspendu, par exemple parce qu'il y a un appel dans un code non géré, GC.Collect() peut ne rien faire du tout.
0 votes
Il existe d'autres raisons de mettre en œuvre Dispose, par exemple, pour libérer la mémoire qui a été allouée, supprimer un élément qui a été ajouté à une collection ou signaler la libération d'un verrou qui a été acquis. Plus de