Je sais, en lisant [la documentation Microsoft] (https://docs.microsoft.com/dotnet/api/system.idisposable), que l'utilisation principale de l'interface IDisposable
est de nettoyer les ressources non gérées.
Pour moi, "non gérées" signifie des choses comme les connexions aux bases de données, les sockets, les poignées de fenêtre, etc. Mais, j'ai vu du code où la méthode Dispose()
est implémentée pour libérer les ressources gérées, ce qui me semble redondant, puisque le garbage collector devrait s'en charger pour vous.
Par exemple :
public class MyCollection : IDisposable
{
private List<String> _theList = new List<String>();
private Dictionary<String, Point> _theDict = new Dictionary<String, Point>();
// Die, clear it up! (free unmanaged resources)
public void Dispose()
{
_theList.clear();
_theDict.clear();
_theList = null;
_theDict = null;
}
Ma question est la suivante : est-ce que cela permet au ramasseur de déchets de libérer la mémoire utilisée par MyCollection
plus rapidement qu'il ne le ferait normalement ?
edit : Jusqu'à présent, les gens ont posté de bons exemples d'utilisation d'IDisposable pour nettoyer les ressources non gérées comme les connexions aux bases de données et les bitmaps. Mais supposons que _theList
dans le code ci-dessus contienne un million de chaînes de caractères, et que vous vouliez libérer cette mémoire maintenant, plutôt que d'attendre le ramasseur de déchets. Le code ci-dessus y parviendrait-il ?
IDisposable
est souvent utilisé pour exploiter l'instruction using
et profiter d'un moyen facile de faire un nettoyage déterministe des objets gérés.
public class LoggingContext : IDisposable {
public Finicky(string name) {
Log.Write("Entering Log Context {0}", name);
Log.Indent();
}
public void Dispose() {
Log.Outdent();
}
public static void Main() {
Log.Write("Some initial stuff.");
try {
using(new LoggingContext()) {
Log.Write("Some stuff inside the context.");
throw new Exception();
}
} catch {
Log.Write("Man, that was a heavy exception caught from inside a child logging context!");
} finally {
Log.Write("Some final stuff.");
}
}
}
Oui, ce code est complètement redondant et inutile, et il n'oblige pas le ramasseur de déchets à faire quoi que ce soit qu'il ne ferait pas autrement (une fois qu'une instance de MyCollection est sortie du champ d'application, c'est à dire.
Réponse à votre modification : en quelque sorte. Si je fais ça :
public void WasteMemory()
{
var instance = new MyCollection(); // this one has no Dispose() method
instance.FillItWithAMillionStrings();
}
// 1 million strings are in memory, but marked for reclamation by the GC
C'est fonctionnellement identique à ceci pour la gestion de la mémoire :
public void WasteMemory()
{
var instance = new MyCollection(); // this one has your Dispose()
instance.FillItWithAMillionStrings();
instance.Dispose();
}
// 1 million strings are in memory, but marked for reclamation by the GC
Si vous avez vraiment vraiment besoin de libérer la mémoire à cet instant précis, appelez GC.Collect()
. Mais il n'y a aucune raison de le faire ici. La mémoire sera libérée quand vous en aurez besoin.
Si MaCollection va de toute façon être récupérée par le ramasseur d'ordures, il n'est pas nécessaire de l'éliminer. Cela ne ferait que faire tourner le processeur plus que nécessaire, et pourrait même invalider une analyse pré-calculée que le ramasseur d'ordures a déjà effectuée.
J'utilise IDisposable
pour m'assurer que les threads sont éliminés correctement, ainsi que les ressources non gérées.
EDIT En réponse au commentaire de Scott :
Le seul moment où les mesures de performance du GC sont affectées est lorsqu'un appel à [sic] GC.Collect() est effectué" _.
D'un point de vue conceptuel, le GC maintient une vue du graphe de référence des objets, et de toutes les références qui y sont faites à partir du tas de données des threads. Cette pile peut être assez grande et couvrir de nombreuses pages de mémoire. Par souci d'optimisation, le GC met en cache son analyse des pages qui ne sont pas susceptibles de changer très souvent afin d'éviter de balayer à nouveau la page inutilement. Le GC reçoit une notification du noyau lorsque les données d'une page changent, il sait donc que la page est sale et nécessite une nouvelle analyse. Si la collection est en Gen0, il est probable que d'autres choses dans la page changent aussi, mais c'est moins probable en Gen1 et Gen2. Anecdotiquement, ces crochets n'étaient pas disponibles sous Mac OS X pour l'équipe qui a porté la GC sur Mac afin que le plug-in Silverlight fonctionne sur cette plateforme.
Un autre point contre l'élimination inutile des ressources : imaginez une situation où un processus se décharge. Imaginez également que le processus est en cours d'exécution depuis un certain temps. Il est probable que de nombreuses pages de mémoire de ce processus ont été échangées sur le disque. Au minimum, elles ne sont plus dans le cache L1 ou L2. Dans une telle situation, il n'y a aucun intérêt pour une application qui se décharge à rééchanger toutes ces pages de données et de code en mémoire pour "libérer" des ressources qui seront de toute façon libérées par le système d'exploitation lorsque le processus se terminera. Ceci s'applique aux ressources gérées et même à certaines ressources non gérées. Seules les ressources qui maintiennent en vie les threads qui ne sont pas en arrière-plan doivent être éliminées, sinon le processus restera en vie.
Maintenant, lors d'une exécution normale, il y a des ressources éphémères qui doivent être nettoyées correctement (comme @fezmonkey le souligne connexions de bases de données, sockets, handles de fenêtres) pour éviter les fuites de mémoire non gérées. C'est le genre de choses qui doivent être éliminées. Si vous créez une classe qui possède un thread (et par "possède", je veux dire qu'elle l'a créé et qu'elle est donc responsable de son arrêt, du moins selon mon style de codage), alors cette classe doit très probablement implémenter IDisposable
et détruire le thread pendant Dispose
.
Le framework .NET utilise l'interface IDisposable
comme un signal, voire un avertissement, aux développeurs que cette classe doit être disposée. Je ne vois aucun type dans le framework qui implémente IDisposable
(à l'exception des implémentations explicites de l'interface) où l'élimination est facultative.