Dois-je supprimer () DataSet et DataTable?

197

DataSet et DataTable implémentent tous deux IDisposable, donc, selon les meilleures pratiques conventionnelles, je devrais appeler leurs méthodes Dispose ().

Cependant, d'après ce que j'ai lu jusqu'à présent, DataSet et DataTable n'ont en fait aucune ressource non gérée, donc Dispose () ne fait pas grand-chose.

De plus, je ne peux pas utiliser simplement using(DataSet myDataSet...)parce que DataSet a une collection de DataTables.

Donc, pour être sûr, je devrais parcourir myDataSet.Tables, disposer de chacun des DataTables, puis disposer du DataSet.

Alors, vaut-il la peine d'appeler Dispose () sur tous mes DataSets et DataTables?

Addenda:

Pour ceux d'entre vous qui pensent que DataSet doit être supprimé: En général, le modèle de suppression est d'utiliser usingou try..finally, car vous voulez garantir que Dispose () sera appelé.

Cependant, cela devient très vite laid pour une collection. Par exemple, que faites-vous si l'un des appels à Dispose () lève une exception? L'avalez-vous (ce qui est "mauvais") pour pouvoir continuer à éliminer l'élément suivant?

Ou, suggérez-vous que j'appelle juste myDataSet.Dispose (), et que j'oublie de supprimer les DataTables dans myDataSet.Tables?

mbeckish
la source
9
Dispose n'est pas censé lever d'exceptions. Si c'est le cas - ce n'est pas bien écrit, alors ... essayez {some.Dispose (); } catch {} devrait suffire. - blogs.msdn.com/b/clyon/archive/2004/09/23/233464.aspx
LukeSw
3
J'ai remarqué une fuite de mémoire apparente dans l'une de mes applications qui utilise beaucoup d'objets DataSet. Je n'avais pas appelé .Dispose () ou utilisé des blocs "using" pour ces objets. Donc, j'ai parcouru le code et ajouté un bloc "à l'aide" à chaque endroit où je créais un DataSet ou un DataTable, et voila la mémoire est maintenant libérée. Cela me semble une indication solide que .Dispose () est, en fait, nécessaire pour DataSet et DataTable.
dizzy.stackoverflow du

Réponses:

147

Voici quelques discussions expliquant pourquoi Dispose n'est pas nécessaire pour un DataSet.

Disposer ou ne pas disposer? :

La méthode Dispose dans DataSet existe UNIQUEMENT en raison des effets secondaires de l'héritage - en d'autres termes, elle ne fait rien d'utile dans la finalisation.

Faut-il appeler Dispose sur les objets DataTable et DataSet? comprend quelques explications d'un MVP:

L'espace de noms system.data (ADONET) ne contient pas de ressources non gérées. Par conséquent, il n'est pas nécessaire de les éliminer tant que vous ne vous y êtes pas ajouté quelque chose de spécial.

Comprendre la méthode Dispose et les jeux de données? a un commentaire de l'autorité Scott Allen:

Dans la pratique, nous éliminons rarement un DataSet car il offre peu d'avantages "

Ainsi, le consensus est qu'il n'y a actuellement aucune bonne raison d'appeler Dispose sur un DataSet.

DOK
la source
7
Les liens fournis ont totalement manqué le point que DataTable est un type d'objet Finalizable. Veuillez voir la réponse de Nariman ci-dessous.
Herman
Réponse intéressante mais qu'en est-il de SqlConnection, SqlCommand et SqlDataAdapter, le Dispose doit-il être appelé explicitement?
Willy
@Willy Je pense que beaucoup de gens utilisent une instruction using pour IDisposables. using (SqlConnection cn = new SqlConnection (connectionString)) {using (SqlCommand cm = new SqlCommand (commandString, cn)) {cn.Open (); cm.ExecuteNonQuery (); }}
DOK
1
@Willy oui, ceux-ci doivent absolument être supprimés car ils utilisent des ressources non gérées. Que ce soit appelé explicitement ou implicitement à l'aide d'un usingbloc, cela dépend de vous.
D Stanley
129

Mise à jour (1er décembre 2009):

Je voudrais modifier cette réponse et admettre que la réponse originale était erronée.

L'analyse initiale ne s'applique aux objets qui nécessitent la finalisation - et le point que les pratiques ne devraient pas être acceptés à la surface sans précision, en profondeur la compréhension est encore debout.

Cependant, il s'avère que DataSets, DataViews, DataTables suppriment la finalisation dans leurs constructeurs - c'est pourquoi appeler Dispose () sur eux explicitement ne fait rien.

Vraisemblablement, cela se produit parce qu'ils n'ont pas de ressources non gérées; donc malgré le fait que MarshalByValueComponent prend en compte les ressources non gérées, ces implémentations particulières n'ont pas besoin et peuvent donc renoncer à la finalisation.

(Le fait que les auteurs de .NET prennent soin de supprimer la finalisation sur les types mêmes qui occupent normalement le plus de mémoire témoigne de l'importance de cette pratique en général pour les types finalisables.)

Néanmoins, le fait que ces détails soient encore sous-documentés depuis la création du .NET Framework (il y a près de 8 ans) est assez surprenant (que vous êtes essentiellement laissé à vos propres appareils pour passer au crible des éléments contradictoires et ambigus pour assembler les éléments est parfois frustrant mais fournit une compréhension plus complète du cadre sur lequel nous nous appuyons tous les jours).

Après beaucoup de lecture, voici ma compréhension:

Si un objet nécessite une finalisation, il pourrait occuper de la mémoire plus longtemps qu'il n'en a besoin - voici pourquoi: a) Tout type qui définit un destructeur (ou hérite d'un type qui définit un destructeur) est considéré comme finalisable; b) Lors de l'allocation (avant l'exécution du constructeur), un pointeur est placé dans la file d'attente de finalisation; c) Un objet finalisable nécessite normalement la récupération de 2 collections (au lieu de la norme 1); d) La suppression de la finalisation ne supprime pas un objet de la file d'attente de finalisation (comme indiqué par! FinalizeQueue dans SOS) Cette commande est trompeuse; Savoir quels objets se trouvent dans la file d'attente de finalisation (en soi) n'est pas utile; Il serait utile de savoir quels objets se trouvent dans la file d'attente de finalisation et nécessitent encore une finalisation (existe-t-il une commande pour cela?)

La suppression de la finalisation désactive un peu dans l'en-tête de l'objet indiquant au runtime qu'il n'a pas besoin d'avoir son Finalizer invoqué (n'a pas besoin de déplacer la file d'attente FReachable); Il reste dans la file d'attente de finalisation (et continue d'être signalé par! FinalizeQueue dans SOS)

Les classes DataTable, DataSet, DataView sont toutes enracinées dans MarshalByValueComponent, un objet finalisable qui peut (potentiellement) gérer des ressources non gérées

  • Parce que DataTable, DataSet, DataView n'introduisent pas de ressources non managées, ils suppriment la finalisation dans leurs constructeurs
  • Bien qu'il s'agisse d'un modèle inhabituel, cela évite à l'appelant d'avoir à se soucier d'appeler Dispose après utilisation
  • Cela, et le fait que les DataTables peuvent potentiellement être partagés entre différents DataSets, est probablement la raison pour laquelle les DataSets ne se soucient pas de supprimer les DataTables enfants
  • Cela signifie également que ces objets apparaîtront sous! FinalizeQueue dans SOS
  • Cependant, ces objets devraient toujours être récupérables après une seule collection, comme leurs homologues non finalisables

4 (nouvelles références):

Réponse originale:

Il y a beaucoup de réponses trompeuses et généralement très pauvres à ce sujet - toute personne qui a atterri ici devrait ignorer le bruit et lire attentivement les références ci-dessous.

Sans aucun doute, Dispose doit être appelé sur tous les objets Finalizable.

Les DataTables sont finalisables.

Calling Dispose accélère considérablement la récupération de mémoire.

MarshalByValueComponent appelle GC.SuppressFinalize (this) dans son Dispose () - ignorer cela signifie avoir à attendre des dizaines sinon des centaines de collections Gen0 avant que la mémoire ne soit récupérée:

Avec cette compréhension de base de la finalisation, nous pouvons déjà déduire des choses très importantes:

Premièrement, les objets qui doivent être finalisés vivent plus longtemps que les autres. En fait, ils peuvent vivre beaucoup plus longtemps. Par exemple, supposons qu'un objet qui se trouve dans gen2 doit être finalisé. La finalisation sera planifiée mais l'objet est toujours dans gen2, il ne sera donc pas recollecté avant la prochaine collecte de gen2. Cela pourrait être très long en effet, et, en fait, si les choses vont bien, ce sera long, car les collections gen2 sont coûteuses et nous voulons donc qu'elles se produisent très rarement. Les objets plus anciens nécessitant une finalisation peuvent devoir attendre des dizaines sinon des centaines de collections gen0 avant de récupérer leur espace.

Deuxièmement, les objets qui doivent être finalisés provoquent des dommages collatéraux. Étant donné que les pointeurs d'objet internes doivent rester valides, non seulement les objets directement à finaliser resteront en mémoire, mais tout ce à quoi l'objet fait référence, directement et indirectement, restera également en mémoire. Si un énorme arbre d'objets était ancré par un seul objet qui nécessitait la finalisation, alors l'arbre entier resterait, potentiellement pendant longtemps comme nous venons de le discuter. Il est donc important d'utiliser les finaliseurs avec parcimonie et de les placer sur des objets qui ont le moins de pointeurs d'objets internes possible. Dans l'exemple d'arbre que je viens de donner, vous pouvez facilement éviter le problème en déplaçant les ressources qui ont besoin de finalisation vers un objet séparé et en gardant une référence à cet objet à la racine de l'arbre.

Enfin, les objets nécessitant une finalisation créent du travail pour le thread de finalisation. Si votre processus de finalisation est complexe, le seul et unique thread de finaliseur passera beaucoup de temps à effectuer ces étapes, ce qui peut entraîner un retard de travail et, par conséquent, la persistance de plus d'objets en attente de finalisation. Par conséquent, il est extrêmement important que les finaliseurs fassent le moins de travail possible. Rappelez-vous également que bien que tous les pointeurs d'objet restent valides pendant la finalisation, il se peut que ces pointeurs conduisent à des objets qui ont déjà été finalisés et peuvent donc être moins qu'utiles. Il est généralement plus sûr d'éviter de suivre les pointeurs d'objet dans le code de finalisation même si les pointeurs sont valides. Un chemin de code de finalisation sûr et court est le meilleur.

Prenez-le de quelqu'un qui a vu des centaines de Mo de DataTables non référencés dans Gen2: cela est extrêmement important et complètement ignoré par les réponses sur ce fil.

Références:

1 - http://msdn.microsoft.com/en-us/library/ms973837.aspx

2 - http://vineetgupta.spaces.live.com/blog/cns!8DE4BDC896BEE1AD!1104.entry http://www.dotnetfunda.com/articles/article524-net-best-practice-no-2-improve-garbage -collector-performance-using-finalizedispose-pattern.aspx

3 - http://codeidol.com/csharp/net-framework/Inside-the-CLR/Automatic-Memory-Management/

Nariman
la source
Bon point. Comment structurez-vous généralement votre code lorsque vous avez un DataSet avec de nombreux DataTables? Des tonnes d'impressions imbriquées? Un seul essai ... enfin pour tout nettoyer en même temps?
mbeckish
14
L'instruction "Cependant, il s'avère que DataSets, DataViews, DataTables suppriment la finalisation dans leurs constructeurs - c'est pourquoi appeler Dipose () sur eux ne fait rien explicitement." est un non-séquentiel: les deux concepts sont largement indépendants; quelque chose qui supprime la finalisation pourrait encore faire quelque chose dans Dispose (). En effet, cela a plus de sens si nous l'inversons: Dispose () ne fait rien, c'est pourquoi il supprime la finalisation dans le constructeur, c'est-à-dire parce qu'il n'y aurait rien à faire, il ne veut pas déranger le GC avec l'appel du finaliseur ( qui appelle généralement disposer).
Marc Gravell
Merci. Cette discussion s'applique-t-elle également au TableAdapters?
Bondolin
24

Vous devez supposer qu'il fait quelque chose d'utile et appeler Dispose même s'il ne fait rien dans le courant. Dans les incarnations de NET Framework, rien ne garantit qu'il en sera ainsi dans les futures versions, ce qui entraînera une utilisation inefficace des ressources.

Nuno
la source
Il n'y a aucune garantie qu'il implémentera IDisposable à l'avenir non plus. Je serais d'accord avec vous si c'était aussi simple que d'utiliser (...), mais dans le cas de DataSet, cela semble être beaucoup de tracas pour rien.
mbeckish
28
Il est assez sûr de supposer qu'il implémentera toujours IDisposable. L'ajout ou la suppression de l'interface est un changement de rupture, contrairement à la modification de l'implémentation de Dispose.
Greg Dean
5
En outre, un autre fournisseur peut avoir une implémentation qui fait réellement quelque chose avec IDisposable.
Matt Spradley
Sans oublier que ce DataTablen'est pas scellé - ce n'est pas grave lorsque vous le faites new DataTable, mais assez important lorsque vous prenez un DataTablecomme argument ou à la suite d'un appel de méthode.
Luaan
17

Même si l'objet n'a pas de ressources non gérées, la suppression peut aider le GC en cassant les graphiques des objets. En général, si l'objet implémente IDisposable, Dispose () doit être appelé.

Que Dispose () fasse réellement quelque chose ou non dépend de la classe donnée. Dans le cas de DataSet, l'implémentation Dispose () est héritée de MarshalByValueComponent. Il se retire du conteneur et appelle l'événement Disposed. Le code source est ci-dessous (démonté avec .NET Reflector):

protected virtual void Dispose(bool disposing)
{
    if (disposing)
    {
        lock (this)
        {
            if ((this.site != null) && (this.site.Container != null))
            {
                this.site.Container.Remove(this);
            }
            if (this.events != null)
            {
                EventHandler handler = (EventHandler) this.events[EventDisposed];
                if (handler != null)
                {
                    handler(this, EventArgs.Empty);
                }
            }
        }
    }
}
dwieczor
la source
1
En effet. J'ai vu récemment du code où de nombreux DataTables ont été créés dans une très grande boucle sans être supprimés. Cela a conduit à toute la mémoire consommée sur l'ordinateur et le processus s'est écrasé car il manquait de mémoire. Après avoir dit au développeur d'appeler disposer sur le DataTable, le problème a disparu.
RichardOD
7

Créez-vous vous-même les DataTables? Parce que l'itération à travers les enfants de n'importe quel objet (comme dans DataSet.Tables) n'est généralement pas nécessaire, car c'est le travail du parent de supprimer tous ses membres enfants.

En règle générale, la règle est la suivante: si vous l'avez créé et qu'il implémente IDisposable, supprimez-le. Si vous ne l'avez PAS créé, ne le supprimez PAS, c'est le travail de l'objet parent. Mais chaque objet peut avoir des règles spéciales, consultez la documentation.

Pour .net 3.5, il dit explicitement "Éliminez-le lorsque vous ne l'utilisez plus", c'est donc ce que je ferais.

Michael Stum
la source
4
D'après ce que je comprends, le consensus général est qu'un objet doit disposer de ses propres ressources non gérées. Cependant, une collection d'objets IDisposable n'itérera pas en général à travers ses éléments pour disposer chacun, car il pourrait y avoir d'autres références à ses éléments en dehors de la collection: stackoverflow.com/questions/496722/…
mbeckish
1
Certes, les collections sont toujours quelque chose que je considère comme spécial car elles ne "font" rien, elles sont simplement ... des conteneurs, donc je ne me suis jamais soucié de ça.
Michael Stum
7

J'appelle dispose chaque fois qu'un objet implémente IDisposeable. C'est là pour une raison.

Les ensembles de données peuvent être de gros porcs de mémoire. Le plus tôt ils peuvent être marqués pour le nettoyage, mieux c'est.

mettre à jour

Cela fait 5 ans que j'ai répondu à cette question. Je suis toujours d'accord avec ma réponse. S'il existe une méthode dispose, elle doit être appelée lorsque vous avez terminé avec l'objet. L'interface IDispose a été implémentée pour une raison.

Chuck Conway
la source
5
Appeler dispose n'accélère pas la récupération de mémoire, pour ce faire, vous devez démarrer manuellement le garbage collector, ce qui est généralement un mauvais plan.
Tetraneutron
2
Si Dispose définit un groupe de références sur null, cela peut faire en sorte que des objets soient candidats à la collection qui pourraient autrement être ignorés.
Greg Dean
1
Le but de Dispose n'est pas de vider la mémoire des objets gérés - c'est le travail du garbage collector. Le but est de nettoyer les objets non gérés. Il semble y avoir des preuves que les DataSets n'ont pas de références non gérées, donc théoriquement, il n'est pas nécessaire de les supprimer. Cela étant dit, je n'ai jamais été dans une situation où j'ai dû faire tout mon possible pour appeler Dispose - je l'appellerais de toute façon.
cbp
4
L' utilisation principale d'IDisposable est de libérer des ressources non gérées. Souvent, il modifie également l'état d'une manière qui a du sens pour une instance supprimée. (c'est-à-dire les propriétés définies sur false, les références définies sur null, etc.)
Greg Dean
3
S'il existe une méthode d'élimination sur un objet, elle y a été placée pour une raison, que ce soit pour nettoyer des objets non gérés ou non.
Chuck Conway
4

Si votre intention ou le contexte de cette question est vraiment un ramasse-miettes, vous pouvez définir explicitement les ensembles de données et les tables de données sur null ou utiliser le mot clé using et les laisser hors de portée. Éliminer ne fait pas grand chose comme Tetraneutron l'a dit plus tôt. GC collectera les objets de l'ensemble de données qui ne sont plus référencés ainsi que ceux qui sont hors de portée.

Je souhaite vraiment que les gens forcés à voter aient réellement écrit un commentaire avant de voter contre la réponse.

Srikar Doddi
la source
+ 1 Je suppose que certaines personnes ne veulent pas permettre aux autres de considérer différents points de vue.
DOK
2
vote à la baisse, n'empêche nullement les gens de considérer des points de vue différents.
Greg Dean
1

Les ensembles de données implémentent IDisposable à fond MarshalByValueComponent, qui implémente IDisposable. Étant donné que les ensembles de données sont gérés, il n'y a aucun avantage réel à appeler Dispose.

Tétraneutron
la source
6
Il peut maintenant, qui sait ce qu'il fera plus tard.
Greg Dean
Cette attitude dans laquelle vous spéculez que tout code ne fera pas à l'avenir ce qu'il est censé faire est une douleur dans l'hypothèse pour toutes les personnes impliquées.
MicroservicesOnDDD
0

Essayez d'utiliser la fonction Clear (). Cela fonctionne très bien pour moi pour l'élimination.

DataTable dt = GetDataSchema();
//populate dt, do whatever...
dt.Clear();
Hasan Savran
la source
0

Pas besoin de disposer () car DataSet hérite de la classe MarshalByValueComponent et MarshalByValueComponent implémente l'interface IDisposable

Sunil Dhappadhule
la source