Soll ich () DataSet und DataTable entsorgen?

197

DataSet und DataTable implementieren beide IDisposable, daher sollte ich nach herkömmlichen Best Practices ihre Dispose () -Methoden aufrufen.

Nach dem, was ich bisher gelesen habe, verfügen DataSet und DataTable jedoch nicht über nicht verwaltete Ressourcen, sodass Dispose () nicht viel bewirkt.

Außerdem kann ich nicht einfach verwenden, using(DataSet myDataSet...)da DataSet eine Sammlung von DataTables enthält.

Um sicher zu gehen, muss ich also myDataSet.Tables durchlaufen, alle DataTables entsorgen und dann das DataSet entsorgen.

Lohnt es sich also, Dispose () für alle meine DataSets und DataTables aufzurufen?

Nachtrag:

Für diejenigen unter Ihnen, die der Meinung sind, dass DataSet entsorgt werden sollte: Im Allgemeinen ist das Entsorgungsmuster die Verwendung von usingoder try..finally, weil Sie sicherstellen möchten, dass Dispose () aufgerufen wird.

Für eine Sammlung wird dies jedoch sehr schnell hässlich. Was tun Sie beispielsweise, wenn einer der Aufrufe von Dispose () eine Ausnahme auslöst? Schlucken Sie es (was "schlecht" ist), damit Sie das nächste Element weiter entsorgen können?

Oder schlagen Sie vor, dass ich einfach myDataSet.Dispose () aufrufe und vergesse, die DataTables in myDataSet.Tables zu entsorgen?

mbeckish
quelle
9
Dispose soll keine Ausnahmen auslösen. Wenn ja, ist es nicht gut geschrieben, also ... versuche {some.Dispose (); } catch {} sollte ausreichen. - blogs.msdn.com/b/clyon/archive/2004/09/23/233464.aspx
LukeSw
3
Ich habe einen offensichtlichen Speicherverlust in einer meiner Apps festgestellt, die viele DataSet-Objekte verwendet. Ich hatte nicht .Dispose () aufgerufen oder "using" -Blöcke für diese Objekte verwendet. Also ging ich den Code durch und fügte jedem Ort, an dem ich ein DataSet oder eine DataTable erstellte, einen "using" -Block hinzu, und voila, der Speicher wird jetzt freigegeben. Scheint mir ein solider Hinweis darauf zu sein, dass .Dispose () tatsächlich für DataSet und DataTable erforderlich ist.
dizzy.stackoverflow

Antworten:

147

Im Folgenden werden einige Diskussionen erläutert, warum Dispose für ein DataSet nicht erforderlich ist.

Entsorgen oder nicht entsorgen? ::

Die Dispose-Methode in DataSet existiert NUR aufgrund von Nebeneffekten der Vererbung - mit anderen Worten, sie führt bei der Finalisierung nichts Nützliches aus.

Sollte Dispose für DataTable- und DataSet-Objekte aufgerufen werden? enthält einige Erklärungen von einem MVP:

Der system.data-Namespace (ADONET) enthält keine nicht verwalteten Ressourcen. Daher müssen Sie keine davon entsorgen, solange Sie sich nicht etwas Besonderes hinzugefügt haben.

Grundlegendes zur Entsorgungsmethode und zu Datensätzen? hat einen mit Kommentar von Autorität Scott Allen:

In der Praxis entsorgen wir ein DataSet selten, weil es wenig Nutzen bietet. "

Es besteht also Konsens darüber, dass es derzeit keinen guten Grund gibt, Dispose in einem DataSet aufzurufen.

DOK
quelle
7
Die bereitgestellten Links haben den Punkt völlig verfehlt, dass DataTable eine Art finalisierbares Objekt ist. Bitte sehen Sie Narimans Antwort unten.
Herman
Interessante Antwort, aber was ist mit SqlConnection, SqlCommand und SqlDataAdapter? Sollte Dispose explizit aufgerufen werden?
Willy
@Willy Ich denke, viele Leute verwenden eine using-Anweisung für IDisposables. using (SqlConnection cn = new SqlConnection (connectionString)) {using (SqlCommand cm = new SqlCommand (commandString, cn)) {cn.Open (); cm.ExecuteNonQuery (); }}
DOK
1
@Willy ja diese sollten unbedingt entsorgt werden, da sie nicht verwaltete Ressourcen verwenden. Ob es explizit oder implizit mit einem usingBlock aufgerufen wird, liegt bei Ihnen.
D Stanley
129

Update (1. Dezember 2009):

Ich möchte diese Antwort ändern und zugeben, dass die ursprüngliche Antwort fehlerhaft war.

Die ursprüngliche Analyse gilt für Objekte, die finalisiert werden müssen - und der Punkt, an dem Praktiken ohne ein genaues, tiefgreifendes Verständnis nicht an der Oberfläche akzeptiert werden sollten, bleibt bestehen.

Es stellt sich jedoch heraus, dass DataSets, DataViews und DataTables die Finalisierung in ihren Konstruktoren unterdrücken. Aus diesem Grund führt der explizite Aufruf von Dispose () für sie nichts aus.

Vermutlich geschieht dies, weil sie nicht über nicht verwaltete Ressourcen verfügen. Trotz der Tatsache, dass MarshalByValueComponent nicht verwaltete Ressourcen berücksichtigt, sind diese speziellen Implementierungen nicht erforderlich und können daher auf die Finalisierung verzichten.

(Dass .NET-Autoren darauf achten würden, die Finalisierung für genau die Typen zu unterdrücken, die normalerweise den meisten Speicher belegen, spricht für die Bedeutung dieser Vorgehensweise im Allgemeinen für finalisierbare Typen.)

Ungeachtet dessen ist es ziemlich überraschend, dass diese Details seit der Einführung von .NET Framework (vor fast 8 Jahren) immer noch nicht ausreichend dokumentiert sind (dass Sie im Wesentlichen Ihren eigenen Geräten überlassen bleiben, um widersprüchliches, mehrdeutiges Material zu sichten, um die Teile zusammenzusetzen ist manchmal frustrierend, bietet aber ein umfassenderes Verständnis des Rahmens, auf den wir uns täglich verlassen).

Nach vielem Lesen verstehe ich Folgendes:

Wenn ein Objekt Abschluss erfordert, es könnte Speicher länger einnehmen als nötig - hier ist der Grund: a) Jede Art , die eine destructor (oder erbt von einem Typ definiert , dass ein destructor definiert) finalizable betrachtet wird; b) Bei der Zuweisung (bevor der Konstruktor ausgeführt wird) wird ein Zeiger auf die Finalisierungswarteschlange gesetzt. c) Für ein finalisierbares Objekt müssen normalerweise 2 Sammlungen zurückgefordert werden (anstelle von Standard 1). d) Durch das Unterdrücken der Finalisierung wird kein Objekt aus der Finalisierungswarteschlange entfernt (wie von! FinalizeQueue in SOS gemeldet). Dieser Befehl ist irreführend. Es ist nicht hilfreich zu wissen, welche Objekte sich in der Finalisierungswarteschlange befinden (an und für sich). Es wäre hilfreich zu wissen, welche Objekte sich in der Finalisierungswarteschlange befinden und noch finalisiert werden müssen (gibt es dafür einen Befehl?)

Das Unterdrücken der Finalisierung wird im Header des Objekts etwas deaktiviert, um der Laufzeit anzuzeigen, dass der Finalizer nicht aufgerufen werden muss (die FReachable-Warteschlange muss nicht verschoben werden). Es bleibt in der Finalisierungswarteschlange (und wird weiterhin von! FinalizeQueue in SOS gemeldet).

Die Klassen DataTable, DataSet und DataView basieren alle auf MarshalByValueComponent, einem finalisierbaren Objekt, das (möglicherweise) nicht verwaltete Ressourcen verarbeiten kann

  • Da DataTable, DataSet und DataView keine nicht verwalteten Ressourcen einführen, unterdrücken sie die Finalisierung in ihren Konstruktoren
  • Dies ist zwar ein ungewöhnliches Muster, befreit den Anrufer jedoch von der Notwendigkeit, Dispose nach der Verwendung anzurufen
  • Dies und die Tatsache, dass DataTables möglicherweise für verschiedene DataSets freigegeben werden können, ist wahrscheinlich der Grund, warum DataSets untergeordnete DataTables nicht entsorgen möchten
  • Dies bedeutet auch, dass diese Objekte in SOS unter! FinalizeQueue angezeigt werden
  • Diese Objekte sollten jedoch nach einer einzigen Sammlung wie ihre nicht finalisierbaren Gegenstücke weiterhin zurückgefordert werden können

4 (neue Referenzen):

Ursprüngliche Antwort:

Es gibt viele irreführende und im Allgemeinen sehr schlechte Antworten darauf - jeder, der hier gelandet ist, sollte den Lärm ignorieren und die folgenden Referenzen sorgfältig lesen.

Ohne Zweifel entsorgen sollte werden auf irgendwelchen Finalizable Objekte bezeichnet.

DataTables sind finalisierbar.

Calling Dispose beschleunigt die Speicherwiederherstellung erheblich .

MarshalByValueComponent ruft GC.SuppressFinalize (this) in Dispose () auf. Wenn Sie dies überspringen, müssen Sie auf Dutzende, wenn nicht Hunderte von Gen0-Sammlungen warten, bevor der Speicher wiederhergestellt wird:

Mit diesem grundlegenden Verständnis der Finalisierung können wir bereits einige sehr wichtige Dinge ableiten:

Erstens leben Objekte, die finalisiert werden müssen, länger als Objekte, die nicht finalisiert werden müssen. Tatsächlich können sie viel länger leben. Angenommen, ein Objekt in Gen2 muss finalisiert werden. Die Finalisierung wird geplant, aber das Objekt befindet sich noch in Gen2, sodass es erst bei der nächsten Gen2-Sammlung erneut erfasst wird. Das könnte in der Tat eine sehr lange Zeit sein, und wenn die Dinge gut laufen, wird es in der Tat eine lange Zeit sein, da Gen2-Sammlungen teuer sind und wir daher möchten, dass sie sehr selten vorkommen. Ältere Objekte, die finalisiert werden müssen, müssen möglicherweise auf Dutzende, wenn nicht Hunderte von gen0-Sammlungen warten, bevor ihr Speicherplatz zurückgefordert wird.

Zweitens verursachen Objekte, die finalisiert werden müssen, Kollateralschäden. Da die internen Objektzeiger gültig bleiben müssen, verbleiben nicht nur die Objekte, die direkt finalisiert werden müssen, im Speicher, sondern alles, worauf sich das Objekt direkt und indirekt bezieht, bleibt auch im Speicher. Wenn ein riesiger Baum von Objekten durch ein einzelnes Objekt verankert würde, das finalisiert werden musste, würde der gesamte Baum möglicherweise für eine lange Zeit verweilen, wie wir gerade besprochen haben. Es ist daher wichtig, Finalizer sparsam zu verwenden und sie auf Objekten zu platzieren, die so wenig interne Objektzeiger wie möglich haben. In dem Baumbeispiel, das ich gerade gegeben habe, können Sie das Problem leicht vermeiden, indem Sie die Ressourcen, die finalisiert werden müssen, in ein separates Objekt verschieben und einen Verweis auf dieses Objekt in der Wurzel des Baums behalten.

Schließlich erstellen Objekte, die finalisiert werden müssen, Arbeit für den Finalizer-Thread. Wenn Ihr Finalisierungsprozess komplex ist, wird der einzige Finalizer-Thread viel Zeit damit verbringen, diese Schritte auszuführen. Dies kann zu einem Arbeitsstau führen und dazu, dass mehr Objekte auf die Finalisierung warten. Daher ist es von entscheidender Bedeutung, dass die Finalisierer so wenig Arbeit wie möglich leisten. Denken Sie auch daran, dass, obwohl alle Objektzeiger während der Finalisierung gültig bleiben, diese Zeiger möglicherweise zu Objekten führen, die bereits finalisiert wurden und daher möglicherweise weniger nützlich sind. Es ist im Allgemeinen am sichersten zu vermeiden, Objektzeigern im Finalisierungscode zu folgen, obwohl die Zeiger gültig sind. Ein sicherer, kurzer Finalisierungscodepfad ist der beste.

Nehmen Sie es von jemandem, der in Gen2 Hunderte von MBs nicht referenzierter DataTables gesehen hat: Dies ist äußerst wichtig und wird von den Antworten in diesem Thread völlig übersehen.

Verweise:

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
quelle
Guter Punkt. Wie strukturieren Sie Ihren Code normalerweise, wenn Sie ein DataSet mit vielen DataTables haben? Tonnenweise mit Anweisungen verschachtelt? Ein einziger Versuch ... endlich alles auf einmal aufzuräumen?
mbeckish
14
Die Anweisung "Es stellt sich jedoch heraus, dass DataSets, DataViews und DataTables die Finalisierung in ihren Konstruktoren unterdrücken. Aus diesem Grund führt der explizite Aufruf von Dipose () auf ihnen nichts." ist eine Nicht-Sequenzierung: Die beiden Konzepte sind weitgehend unabhängig voneinander; Etwas, das die Finalisierung unterdrückt, könnte in Dispose () noch etwas bewirken. Tatsächlich ist es sinnvoller, wenn wir es umkehren: Dispose () macht nichts, weshalb es die Finalisierung im Konstruktor unterdrückt, dh weil es nichts zu tun gibt, möchte es den GC nicht mit dem Aufruf des Finalisierers belästigen ( was normalerweise dispose nennt).
Marc Gravell
Vielen Dank. Gilt diese Diskussion auch für TableAdapters?
Bondolin
24

Sie sollten davon ausgehen, dass es etwas Nützliches tut, und Dispose aufrufen, auch wenn es im Moment nichts tut. Inkarnationen von NET Framework gibt es keine Garantie dafür, dass dies in zukünftigen Versionen so bleibt, was zu einer ineffizienten Ressourcennutzung führt.

Nuno
quelle
Es gibt auch keine Garantie dafür, dass IDisposable in Zukunft implementiert wird. Ich würde Ihnen zustimmen, wenn es so einfach wäre wie (...), aber im Fall von DataSet scheint es viel Ärger umsonst zu sein.
mbeckish
28
Es ist ziemlich sicher anzunehmen, dass IDisposable immer implementiert wird. Das Hinzufügen oder Entfernen der Schnittstelle ist eine wichtige Änderung, das Ändern der Implementierung von Dispose jedoch nicht.
Greg Dean
5
Ein anderer Anbieter verfügt möglicherweise auch über eine Implementierung, die tatsächlich etwas mit IDisposable tut.
Matt Spradley
Ganz zu schweigen davon, dass dies DataTablenicht besiegelt ist - keine große Sache, wenn Sie dies tun new DataTable, aber sehr wichtig, wenn Sie a DataTableals Argument oder als Ergebnis eines Methodenaufrufs verwenden.
Luaan
17

Selbst wenn das Objekt keine nicht verwalteten Ressourcen hat, kann das Entsorgen der GC helfen, indem Objektdiagramme beschädigt werden. Wenn das Objekt IDisposable implementiert, sollte im Allgemeinen Dispose () aufgerufen werden.

Ob Dispose () tatsächlich etwas tut oder nicht, hängt von der jeweiligen Klasse ab. Im Fall von DataSet wird die Dispose () - Implementierung von MarshalByValueComponent geerbt. Es entfernt sich aus dem Container und ruft das Ereignis Disposed auf. Der Quellcode ist unten (mit .NET Reflector zerlegt):

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
quelle
1
Tatsächlich. Ich habe kürzlich Code gesehen, in dem viele DataTables in einer sehr großen Schleife erstellt wurden, ohne dass sie entsorgt wurden. Dies führte dazu, dass der gesamte Speicher des Computers belegt wurde und der Prozess abstürzte, da der Speicher knapp wurde. Nachdem ich dem Entwickler gesagt hatte, er solle dispose auf der DataTable aufrufen, war das Problem behoben.
RichardOD
7

Erstellen Sie die DataTables selbst? Da das Durchlaufen der untergeordneten Elemente eines Objekts (wie in DataSet.Tables) normalerweise nicht erforderlich ist, ist es Aufgabe des übergeordneten Elements, alle untergeordneten Mitglieder zu entsorgen.

Im Allgemeinen lautet die Regel: Wenn Sie es erstellt haben und es IDisposable implementiert, entsorgen Sie es. Wenn Sie es NICHT erstellt haben, entsorgen Sie es NICHT, das ist die Aufgabe des übergeordneten Objekts. Für jedes Objekt gelten jedoch möglicherweise spezielle Regeln. Überprüfen Sie die Dokumentation.

Für .net 3.5 heißt es explizit "Entsorgen, wenn es nicht mehr verwendet wird", also würde ich das tun.

Michael Stum
quelle
4
Soweit ich weiß, besteht allgemeiner Konsens darüber, dass ein Objekt über seine eigenen nicht verwalteten Ressourcen verfügen sollte. Eine Sammlung von IDisposable-Objekten durchläuft jedoch im Allgemeinen nicht ihre Elemente, um jedes einzelne zu entsorgen, da möglicherweise andere Verweise auf ihre Elemente außerhalb der Sammlung vorhanden sind: stackoverflow.com/questions/496722/…
mbeckish
1
Es stimmt, Sammlungen sind immer etwas Besonderes, weil sie normalerweise nichts "tun", sondern nur ... Container, also habe ich mich nie darum gekümmert.
Michael Stum
7

Ich rufe dispose immer dann auf, wenn ein Objekt IDisposeable implementiert. Es ist aus einem Grund da.

DataSets können riesige Speicherfresser sein. Je früher sie zum Aufräumen markiert werden können, desto besser.

aktualisieren

Es ist 5 Jahre her, seit ich diese Frage beantwortet habe. Ich stimme meiner Antwort immer noch zu. Wenn es eine Dispose-Methode gibt, sollte diese aufgerufen werden, wenn Sie mit dem Objekt fertig sind. Die IDispose-Schnittstelle wurde aus einem bestimmten Grund implementiert.

Chuck Conway
quelle
5
Wenn Sie dispose aufrufen, wird die Speicherwiederherstellung nicht beschleunigt. Dazu müssten Sie den Garbage Collector manuell starten, was im Allgemeinen ein schlechter Plan ist.
Tetraneutron
2
Wenn Dispose eine Reihe von Verweisen auf null setzt, kann dies dazu führen, dass Objekte Kandidaten für die Sammlung sind, die andernfalls möglicherweise übersprungen werden.
Greg Dean
1
Der Zweck der Entsorgung besteht nicht darin, den Speicher verwalteter Objekte zu löschen - das ist die Aufgabe des Garbage Collectors. Es geht darum, nicht verwaltete Objekte zu löschen. Es scheint Beweise dafür zu geben, dass DataSets keine nicht verwalteten Referenzen haben, so dass sie theoretisch nicht entsorgt werden müssen. Davon abgesehen war ich noch nie in einer Situation, in der ich mich sehr bemühen musste, Dispose anzurufen - ich würde es trotzdem nennen.
cbp
4
Die primäre Verwendung von IDisposable ist nicht verwalteten Ressourcen freizugeben. Oft ändert es auch den Zustand auf eine Weise, die für eine entsorgte Instanz sinnvoll ist. (dh Eigenschaften auf false gesetzt, Referenzen auf null gesetzt usw.)
Greg Dean
3
Wenn für ein Objekt eine Entsorgungsmethode vorhanden ist, wurde diese aus einem bestimmten Grund dort abgelegt, unabhängig davon, ob nicht verwaltete Objekte bereinigt werden sollen oder nicht.
Chuck Conway
4

Wenn Ihre Absicht oder der Kontext dieser Frage wirklich die Speicherbereinigung ist, können Sie die Datasets und Datentabellen explizit auf null setzen oder das Schlüsselwort using verwenden und sie außerhalb des Gültigkeitsbereichs lassen. Dispose macht nicht viel, wie Tetraneutron es früher gesagt hat. GC sammelt Datensatzobjekte, auf die nicht mehr verwiesen wird, sowie solche, die außerhalb des Gültigkeitsbereichs liegen.

Ich wünschte wirklich, SO hätte die Leute gezwungen, einen Kommentar zu schreiben, bevor sie die Antwort ablehnen.

Srikar Doddi
quelle
+ 1 Ich denke, einige Leute wollen nicht zulassen, dass andere unterschiedliche Sichtweisen berücksichtigen.
DOK
2
Down Voting verbietet es den Menschen in keiner Weise, unterschiedliche Sichtweisen zu berücksichtigen.
Greg Dean
1

Datensätze implementieren IDisposable gründlich MarshalByValueComponent, das IDisposable implementiert. Da Datasets verwaltet werden, hat der Aufruf von dispose keinen wirklichen Vorteil.

Tetraneutron
quelle
6
Es kann jetzt sein, wer weiß, was es später tun wird.
Greg Dean
Diese Haltung, in der Sie spekulieren, dass ein Code in Zukunft nicht das tun wird, was er tun soll, ist für alle Beteiligten ein Schmerz in der Annahme.
MicroservicesOnDDD
0

Versuchen Sie, die Funktion Clear () zu verwenden. Es funktioniert gut für mich zum Entsorgen.

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

Dispose () ist nicht erforderlich, da DataSet die MarshalByValueComponent-Klasse erbt und MarshalByValueComponent die IDisposable-Schnittstelle implementiert

Sunil Dhappadhule
quelle