我应该Dispose() DataSet和DataTable吗?

216

DataSet和DataTable都实现了IDisposable接口,因此按照传统最佳实践,我应该调用它们的Dispose()方法。

然而,从我迄今为止所读到的内容来看,DataSet和DataTable实际上没有任何非托管资源,因此Dispose()并没有实际作用。

此外,我不能仅仅使用using(DataSet myDataSet...) ,因为DataSet有一个DataTable集合。

因此,为了安全起见,我需要遍历myDataSet.Tables,处理每个DataTable,然后处理DataSet。

所以,是否值得在所有的DataSets和DataTables上调用Dispose()这么麻烦呢?

附录:

对于那些认为DataSet应该被销毁的人们: 通常,处理销毁对象的模式是使用usingtry..finally,因为你想要保证Dispose()将被调用。

但是,对于集合来说,这很快变得混乱。例如,如果其中一个Dispose()的调用引发异常怎么办?您会忽略它(这是“不好”的),以便可以继续处理下一个元素吗?

还是建议我只调用myDataSet.Dispose(),而忽略在myDataSet.Tables中销毁DataTables的操作呢?


10
Dispose不应该抛出任何异常。如果它做到了,那么它就写得不好,所以...try { some.Dispose(); } catch {}足矣。 - LukeSw
1
在实际应用中,并不是所有类都需要实现Dispose()方法,这会引起很多困惑。我只希望MSDN能够更好地记录这些类的特殊性质。 - Krugar
这并不是严格意义上的没有必要。只是在这个时刻没有未管理的资源。其他实现可能会引入它们。你需要决定什么是最好的,但要知道,在微软实现中,Dispose 最坏的情况下很可能只是一个无操作。 - user7116
7
我注意到我的一个应用程序中存在明显的内存泄漏问题,该应用程序使用了大量的 DataSet 对象。我之前没有调用 .Dispose() 方法或使用 "using" 块来处理这些对象。因此,我检查了代码,并在创建 DataSet 或 DataTable 的每个地方添加了 "using" 块,结果内存得以释放。这对我来说是一个坚实的证明:针对 DataSet 和 DataTable ,.Dispose() 方法确实是必需的。 - blitz_jones
12个回答

163
以下是关于为什么 DataSet 不需要使用 Dispose 的讨论: 是否需要使用 Dispose? 中指出,DataSet 中的 Dispose 方法只是因为继承的副作用而存在,换句话说,它在最终化时实际上并没有什么有用的作用。 DataTable 和 DataSet 对象是否需要调用 Dispose? 包括一些来自 MVP 的解释: 系统数据命名空间(ADONET)不包含非托管资源。因此,只要您没有添加特殊内容,就无需处理任何其中的对象。 理解 Dispose 方法和数据集? 有一条来自权威 Scott Allen 的评论:“实际上我们很少会对 DataSet 进行 Dispose,因为它几乎没有任何好处。”
因此,共识是目前没有充分的理由去调用 DataSet 的Dispose方法。

7
提供的链接完全没有指出DataTable是Finalizable对象的一种。请参见下面的Nariman答案。 - Herman
1
有趣的回答,但是SqlConnection、SqlCommand和SqlDataAdapter呢?应该显式调用Dispose吗? - Willy
2
@Willy 是的,它们应该被处理掉,因为它们使用了非托管资源。无论是显式调用还是隐式使用 using 块,都由您决定。 - D Stanley

138
更新(2009年12月1日):
我想修改这个答案并承认原始答案存在缺陷。
原始分析确实适用于需要终结的对象,而且仍然存在一点:不应该只凭表面接受做法,而是需要准确深入地理解。
然而,事实证明,数据集、数据视图、数据表在构造函数中抑制了终结 - 这就是为什么显式调用Dispose()对它们无效的原因。
据推测,这是因为它们没有非托管资源;因此,尽管MarshalByValueComponent允许非托管资源,但这些特定的实现没有这个需求,可以放弃终止。
(.NET的作者会特别抑制终结在通常占用最多内存的类型上,说明这种练习在最终化类型方面的重要性。)
尽管如此,自.NET Framework问世以来(将近8年),这些细节仍然未得到充分记录,这是相当令人惊讶的(你基本上要靠自己去筛选矛盾、模棱两可的材料来整合框架的完整理解,在某些时候会感到沮丧,但确实提供了更全面的框架理解)。
经过大量阅读,我现在理解如下:
如果一个对象需要终结,则它可能会占用比必要时间更长的内存 - 原因如下:a)任何定义了析构函数(或从定义了析构函数的类型继承的类型)的类型都被视为可终结;b)在分配时(在构造函数运行之前),将指针放置在终结队列上;c)通常需要2次收集才能回收最终化对象(而不是标准1次);d)抑制终结并不会从终结队列中移除对象(根据!FinalizeQueue报告)此命令是误导性的;仅知道哪些对象在终结队列上(本身)并没有帮助;只有知道哪些对象在终结队列上且仍然需要终结才有帮助(是否有这样的命令?)

抑制终结会关闭对象头中的一位,向运行时表明它不需要调用它的终结器(不需要移动FReachable队列);它仍然在终结队列上(并继续由!FinalizeQueue在SOS中报告)。
DataTable、DataSet、DataView类都以MarshalByValueComponent为根,这是一个可以(潜在地)处理非托管资源的最终化对象。
  • 因为DataTable、DataSet和DataView不会引入非托管资源,所以它们在构造函数中抑制了终结
  • 虽然这是一种不寻常的模式,但它使调用者无需担心在使用后调用Dispose
  • 这也意味着这些对象将出现在!FinalizeQueue下
  • 这个特性以及DataTable可能会被共享到不同的DataSets中,很可能是DataSets不关心释放子DataTable的原因
  • 然而,这些对象仍然应该在单次垃圾回收后可以被回收,就像它们的非终结对应物一样

4 (新参考文献):

原始回答:

这里有很多误导性的和通常非常差劣的答案 - 任何来到这里的人都应该忽略噪音,仔细阅读下面的参考文献。

毫无疑问,任何可终结对象Dispose后都应该被调用。

DataTables是可终结的。

调用Dispose显著地加速了内存回收。

MarshalByValueComponent在其Dispose()中调用了< strong>GC.SuppressFinalize(this) - 跳过此步骤意味着需要等待数十甚至数百个Gen0集合才能回收内存:

有了对finalization的基本理解,我们就可以推断出一些非常重要的事情:
首先,需要finalization的对象的生命周期比不需要finalization的对象长。实际上,它们可以活得更久。例如,假设一个在gen2中的对象需要finalization。finalization将被安排,但是对象仍然在gen2中,因此直到下一次gen2收集发生时,它才不会被重新收集。这可能需要很长时间,如果一切顺利,它将需要很长时间,因为gen2收集成本很高,因此我们希望它们非常少发生。需要finalization的旧对象可能需要等待几十甚至数百次gen0收集,才能回收它们的空间。
其次,需要finalization的对象会造成副作用。由于内部对象指针必须保持有效,不仅需要直接需要finalization的对象留存在内存中,而且所有对象直接或间接引用的东西也将留在内存中。如果一个巨大的对象树由需要finalization的单个对象锚定,那么整个树都将滞留,可能需要很长时间,就像我们刚才讨论的那样。因此,使用finalizers时要谨慎,并尽可能将其放置在具有尽可能少的内部对象指针的对象上。在我刚才给出的树的例子中,您可以通过将需要finalization的资源移动到单独的对象中并在树的根部保留对该对象的引用来轻松避免问题。通过这个简单的更改,只有一个对象(希望是一个很小的对象)会滞留,并且最小化了finalization的成本。
最后,需要finalization的对象会为finalizer线程创建工作。如果您的finalization过程很复杂,唯一的finalizer线程将花费大量时间执行这些步骤,这可能会导致工作积压,因此会导致更多的对象等待finalization而滞留。因此,非常重要的是,finalizers尽可能少地进行工作。还要记住,虽然在finalization期间所有对象指针仍然有效,但可能存在这样一种情况,即这些指针引导到已经完成finalization的对象,因此可能没有用处。通常最安全的做法是避免在finalization代码中跟随对象指针,即使这些指针是有效的。安全、简短的finalization代码路径是最好的。
从看到Gen2中数百MB未被引用的DataTable的人那里得知:这非常重要,而且这个主题上的答案完全忽略了这一点。
参考资料:

1 - http://msdn.microsoft.com/zh-cn/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/chinese/csharp/net-framework/内部组件和CLR/Automatic-Memory-Management/


好观点。当您有许多数据表的数据集时,通常如何构建代码结构?大量嵌套的using语句?还是一个try..finally来一次性清理所有内容? - mbeckish
19
这句话:“然而,DataSets、DataViews和 DataTables 在构造函数中抑制了终结处理 - 这就是为什么显式调用 Dipose() 不起作用的原因。” 是一种无关紧要的推论:这两个概念在很大程度上是不相关的;抑制终止处理的东西仍然可以在 Dispose() 中做一些事情。事实上,如果我们反过来看,它更有意义:Dispose() 什么也不做,这就是为什么 它在构造函数中抑制终结处理的原因,即因为没有要执行的操作,它不想麻烦垃圾回收器调用终结处理程序(通常会调用 Dispose)。 - Marc Gravell
谢谢。这个讨论是否也适用于“TableAdapter”? - Bondolin

27

即使在当前.NET Framework的版本中,您应该假定Dispose会执行一些有用的操作并进行调用,即使它在当前版本中什么也不做。 不能保证在未来的版本中将保持不变,这可能导致资源使用效率低下。


未来也不能保证它会实现IDisposable接口。如果只是使用(...)这么简单的话,我会同意你的看法,但在DataSet的情况下,这似乎是徒劳无功的麻烦。 - mbeckish
31
可以假定它会始终实现IDisposable接口。添加或删除该接口将会导致破坏性变更,而更改Dispose的实现则不会。 - Greg Dean
5
另外,不同的提供程序可能会具有实际使用IDisposable的实现。 - Matt Spradley
1
更不用说DataTable没有被密封 - 当你使用new DataTable时并不是什么大问题,但当将DataTable作为参数或方法调用的结果时,这就非常重要了。 - Luaan

19

即使一个对象没有非托管资源,调用Dispose()也可以通过断开对象图来帮助垃圾回收。一般来说,如果一个对象实现了IDisposable,应该调用Dispose()。

Dispose()方法是否有实际作用取决于具体的类。在DataSet类的情况下,Dispose()方法的实现是从MarshalByValueComponent继承而来的。它会将自身从容器中移除并调用Disposed事件。以下是源代码(使用.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);
                }
            }
        }
    }
}

2
确实。我最近看到一些代码,在一个非常大的循环中创建了许多DataTable,但没有被Dispose。这导致计算机上的所有内存都被消耗掉,进程因为内存不足而崩溃。在我告诉开发人员对DataTable调用Dispose后,问题得到了解决。 - RichardOD

9
你是自己创建的 DataTables 吗?因为通常不需要遍历任何对象(如 DataSet.Tables)的子元素,因为其父级会处理所有子成员的释放工作。
一般来说,规则是:如果你创建了它并且它实现了 IDisposable 接口,则应该 Dispose 它。如果你没有创建它,则不要 Dispose 它,这是父对象的工作。但每个对象可能有特殊规则,请查看文档。
对于 .NET 3.5,明确指出“在不使用时请将其 Dispose”,所以我会这样做。

4
据我所了解,普遍共识认为一个对象应该清理自己的非托管资源。然而,一个包含IDisposable对象的集合通常不会迭代其元素以逐个处理它们,因为集合外可能存在对这些元素的其他引用。 - mbeckish
1
真的,集合总是我认为很特别的东西,因为它们通常不会“做”任何事情,它们只是...容器,所以我从来没有关心过它。 - Michael Stum

8

每当一个对象实现了IDisposeable接口时,我都会调用dispose方法。这是有原因的。

DataSets可能会占用大量内存。它们越早被标记清除,就越好。

更新

距离我回答这个问题已经过去5年了。我仍然同意我的回答。如果有一个dispose方法,那么在使用完对象后应该调用它。IDispose接口是有原因实现的。


5
调用dispose(释放资源)并不能加速内存回收,要做到这一点,您需要手动启动垃圾收集器,但通常这不是一个好主意。 - Tetraneutron
3
如果Dispose将一堆引用设置为null,这可能导致本来可以跳过的对象成为垃圾回收的候选对象。 - Greg Dean
2
Dispose 的目的不是为了清除托管对象的内存 - 这是垃圾回收器的工作。它的目的是清除非托管对象。有证据表明,DataSets 没有任何非托管引用,因此理论上不需要调用 Dispose。话虽如此,我从未遇到过必须特意调用 Dispose 的情况 - 我总是会调用它。 - cbp
5
IDisposable 的主要用途是释放非托管资源。通常它还会以一种对已释放实例有意义的方式修改状态(例如将属性设置为 false,将引用设置为 null 等)。 - Greg Dean
3
如果一个对象有一个dispose方法,那么它之所以存在是有原因的,无论是用于清理非托管对象还是其他目的。 - Chuck Conway
大家好。 现在,我们可以在“https://referencesource.microsoft.com/#System.Data/fx/src/data/System/Data/DataSet.cs,94665cdea302b675”上检查源代码。 “DataSet”没有覆盖“Dispose”方法,它来自“MarshalByValueComponent”。 - haiduong87

6
如果您的意图或问题的背景确实与垃圾收集有关,则可以显式将数据集和数据表设置为null,或使用关键字using并让它们超出范围。像Tetraneutron之前所说的那样,Dispose并没有太大作用。GC会收集不再被引用以及超出范围的数据集对象。我真的希望SO能强制要求投票人在对答案进行投票之前先写下评论。

我猜有些人不想让别人考虑不同的观点。 - DOK
3
Down voting并不妨碍人们考虑不同的观点。 - Greg Dean

1

数据集通过实现MarshalByValueComponent来实现IDisposable。由于数据集是托管的,因此调用dispose没有真正的好处。


7
现在可能会这样,谁知道以后会怎么样。 - Greg Dean
你这种怀疑任何代码未来都不能按照预期工作的态度,对所有相关人员来说都是一种困扰。 - MicroservicesOnDDD

0
尝试使用Clear()函数。 对于处理它非常有效。
DataTable dt = GetDataSchema();
//populate dt, do whatever...
dt.Clear();

0
不需要调用Dispose()方法,因为DataSet继承了MarshalByValueComponent类,而MarshalByValueComponent实现了IDisposable接口。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接