我应该Dispose()DataSet和DataTable吗?


195

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

但是,到目前为止,从我读过的内容来看,DataSet和DataTable实际上没有任何非托管资源,因此Dispose()实际上并没有做什么。

另外,我不能仅仅using(DataSet myDataSet...)因为DataSet有DataTables的集合而使用。

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

因此,值得在我所有的DataSet和DataTable上调用Dispose()的麻烦吗?

附录:

对于那些认为应该处置DataSet的用户:通常,处置模式是使用usingtry..finally,因为您要保证将调用Dispose()。

但是,对于集合而言,这很快变得很难看。例如,如果对Dispose()的调用之一抛出异常,该怎么办?您是否吞下了它(这是“不好的”),以便您可以继续处理下一个元素?

或者,您是否建议我只调用myDataSet.Dispose(),而忘记将DataTables放置在myDataSet.Tables中?


9
处置不应该引发任何异常。如果这样的话-写得不好,所以……尝试{some.Dispose(); } catch {}应该足够了。- blogs.msdn.com/b/clyon/archive/2004/09/23/233464.aspx
LukeSw

3
我注意到我的一个使用大量DataSet对象的应用程序中明显存在内存泄漏。我没有为那些对象调用.Dispose()或使用“ using”块。因此,我遍历了代码,并在我创建DataSet或DataTable的每个位置添加了一个“ using”块,现在已经释放了内存。在我看来,确实表明.Dispose()实际上对于DataSet和DataTable是必需的。
dizzy.stackoverflow

Answers:


147

这里有一些讨论,解释了为什么对于数据集不需要Dispose。

处置还是不处置?

DataSet中的Dispose方法仅由于继承的副作用而存在-换句话说,它在终结处理中实际上没有任何用处。

应该在DataTable和DataSet对象上调用Dispose吗?包括来自MVP的一些解释:

system.data命名空间(ADONET)不包含非托管资源。因此,只要您还没有添加一些特殊的东西,就不需要处理这些东西。

了解Dispose方法和数据集?有权威斯科特·艾伦的评论:

实际上,我们很少处理数据集,因为它几乎没有好处”

因此,共识是当前没有充分的理由在DataSet上调用Dispose。


6
提供的链接完全错失了DataTable是Finalizable对象的一种类型。请在下面查看Nariman的答案。
赫尔曼(Herman)

有趣的答案,但是关于SqlConnection,SqlCommand和SqlDataAdapter的问题,应该显式调用Dispose吗?
威利2014年

@Willy我认为很多人对IDisposables使用using语句。使用(SqlConnection cn =新的SqlConnection(connectionString)){使用(SqlCommand cm =新的SqlCommand(commandString,cn)){cn.Open(); cm.ExecuteNonQuery(); }
DOK

1
@Willy是的,那些绝对应该被处置,因为它们使用不受管的资源。使用using块是显式调用还是隐式调用都取决于您。
D Stanley

128

更新(2009年12月1日):

我想修改此答案,并承认原始答案有缺陷。

原始分析的确适用于需要最终确定的对象-而且一点仍然是,如果没有准确,深入的理解,就不应在表面上接受实践。

但是,事实证明,数据集,数据视图,数据表会抑制其构造函数中的终结处理–这就是为什么在它们上显式调用Dispose()不会执行任何操作的原因。

据推测,发生这种情况是因为他们没有不受管的资源;因此,尽管MarshalByValueComponent允许对非托管资源进行了分配,但这些特定的实现并不需要,因此可以放弃最终确定。

(.NET作者会小心翼翼地抑制通常占用最多内存的那些类型的最终确定,这说明这种做法通常对于可终结类型很重要。)

尽管如此,自.NET Framework诞生(将近8年前)以来,这些细节仍未得到充分的文档记录(令人惊讶的是,实际上,您留给自己的设备来筛选冲突,模棱两可的材料以将各个部分放在一起)有时会令人沮丧,但确实提供了对我们每天依赖的框架的更完整的理解)。

经过大量阅读,这是我的理解:

如果对象需要终结处理,那么它可能会占用比所需时间更长的内存–这就是原因:a)任何定义了析构函数的类型(或从定义了析构函数的类型继承的类型)都被视为可终结的;b)在分配时(在构造函数运行之前),将指针放置在完成队列上;c)可终结对象通常需要回收2个集合(而不是标准1);d)禁止终结处理不会从终结处理队列中删除对象(如SOS中的!FinalizeQueue所报告)。知道终结队列上的对象本身(本身)并没有帮助;知道哪些对象在终结队列中并且仍然需要终结将很有帮助(是否有此命令?)

取消终结处理会使对象的标头中的位关闭一点,从而向运行时指示它不需要调用其终结器(不需要移动FReachable队列);它保留在完成队列中(并继续由SOS中的!FinalizeQueue报告)。

DataTable,DataSet和DataView类都植根于MarshalByValueComponent,MarshalByValueComponent是一个可终结的对象,可以(潜在地)处理非托管资源

  • 因为DataTable,DataSet,DataView不会引入非托管资源,所以它们抑制了构造函数中的最终确定
  • 尽管这是一个不寻常的模式,但它使调用者不必担心在使用后调用Dispose。
  • 这以及DataTables可能在不同的DataSet之间共享的事实,很可能是为什么DataSet不在意处置子DataTables的原因
  • 这也意味着这些对象将出现在SOS的!FinalizeQueue下
  • 但是,这些对象在一次收集后仍应可回收,就像它们不可最终完成的对等一样

4(新参考):

原始答案:

关于此问题有很多误导且通常很差的答案-降落在此处的任何人都应该忽略噪音,并仔细阅读以下参考资料。

毫无疑问,应该在任何Finalizable对象上调用Dispose 。

数据表是可终结的。

调用Dispose可以显着加快内存回收速度。

MarshalByValueComponent在其Dispose()中调用GC.SuppressFinalize(this) -跳过这意味着在回收内存之前必须等待数十个(即使不是数百个)Gen0集合:

有了对完成的基本理解,我们已经可以推断出一些非常重要的事情:

首先,需要终结处理的对象比不需要终结处理的对象生存更长的时间。实际上,它们的寿命更长。例如,假设第2代中的一个对象需要完成。终结将被安排,但该对象仍在gen2中,因此直到下一个gen2收集发生之前,它不会被重新收集。确实,这可能是很长的时间,而且实际上,如果一切顺利,那将是很长的时间,因为第二代收集成本很高,因此我们希望它们很少出现。需要回收的较旧对象可能需要等待数十个(即使不是数百个)gen0集合,然后才能回收它们的空间。

其次,需要最终确定的对象会造成附带损害。由于内部对象指针必须保持有效,因此不仅直接需要终结处理的对象将在内存中徘徊,而且对象直接或间接引用的所有内容也将保留在内存中。如果一棵巨大的对象树由需要终结的单个对象锚定,则整个树将徘徊不前,可能就像我们刚才讨论的那样持续很长时间。因此,重要的是要谨慎使用终结器,并将其放置在内部对象指针尽可能少的对象上。在我刚才给出的树示例中,您可以通过将需要完成的资源移动到一个单独的对象并将对该对象的引用保留在树的根中来轻松避免该问题。

最后,需要终结处理的对象为终结器线程创建工作。如果终结处理是一个复杂的过程,那么唯一的终结处理线程将花费大量时间来执行这些步骤,这可能导致工作积压,并导致更多的对象徘徊在等待终结的过程。因此,终结器完成尽可能少的工作至关重要。还要记住,尽管在完成过程中所有对象指针仍然有效,但可能是因为这些指针指向已经完成的对象,因此可能没有用。通常,即使指针有效,也要避免在终结代码中跟随对象指针。安全,简短的最终代码路径是最好的。

从在Gen2中看到100 MB的未引用DataTables的人那里获取数据:这非常重要,并且该线程的答案完全将其遗漏了。

参考文献:

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收集器性能使用finalizedispose-pattern.aspx

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


好点子。当您的数据集包含许多数据表时,通常如何构造代码?大量嵌套的using语句?一次try..finally一次将其全部清理干净?
mbeckish

14
语句“但是,事实证明,数据集,数据视图,数据表会抑制其构造函数中的终结处理–这就是为什么在它们上调用Dipose()会显式执行任何操作的原因。” 这是不合时宜的说法:这两个概念在很大程度上无关。抑制最终化的功能仍然可以在Dispose()中完成。确实,如果我们将其反转,则实际上更有意义:Dispose()不执行任何操作,这就是为什么它在构造函数中抑制了终结处理,即,因为无所事事,所以它不想通过调用终结处理程序来打扰GC(通常称为处置)。
Marc Gravell

谢谢。此讨论也适用于TableAdapter吗?
Bondolin


24

您应该假定它执行了一些有用的操作,并且即使当前不执行任何操作,也请调用Dispose。NET Framework的形式,不能保证它将在将来的版本中保持这种状态,从而导致资源使用效率低下。


也不能保证它将来也会实现IDisposable。如果它像using(...)一样简单,我会同意您的意见,但是对于DataSet来说,似乎毫无用处就很麻烦。
mbeckish

28
可以肯定地说,它将始终实现IDisposable。添加或删除接口是一项重大更改,而更改Dispose的实现则不是。
格雷格·迪恩

5
同样,不同的提供程序可能具有使用IDisposable实际执行某些操作的实现。
马特·斯普拉德利

更不用说这DataTable不是密封的-做的时候没什么大不了的new DataTable,但是当将a DataTable作为参数或方法调用的结果时,这很重要。
a安

17

即使对象没有不受管理的资源,处置也可能通过破坏对象图来帮助GC。通常,如果对象实现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);
                }
            }
        }
    }
}

1
确实。我最近看到了一些代码,其中许多数据表是在非常大的循环中创建的,没有被处理掉。这导致计算机上的所有内存被消耗,并且由于内存不足而导致进程崩溃。在我告诉开发人员调用在DataTable上处理后,问题消失了。
RichardOD

7

您是否自己创建DataTables?因为通常不需要遍历任何Object的子级(如DataSet.Tables中的那样),因为处理所有其子级成员是Parent的工作。

通常,规则是:如果创建它并实现IDisposable,则将其处置。如果您没有创建它,那么请不要对其进行处置,这就是父对象的工作。但是每个对象可能都有特殊的规则,请查阅文档。

对于.net 3.5,它明确表示“不再使用时将其丢弃”,这就是我要做的。


4
据我了解,普遍的共识是对象应处置其自身的非托管资源。然而,IDisposable的对象的集合将不通过其要素一般迭代处置各一个,因为有可能是它的元素集合以外的其他参考资料: stackoverflow.com/questions/496722/...
mbeckish

1
的确,收藏始终是我认为很特别的东西,因为它们通常不“做”任何事情,它们只是...容器,所以我从不为此而烦恼。
迈克尔·斯托姆

7

每当对象实现IDisposeable时,我就称其为Dispose。在那里是有原因的。

数据集可能是巨大的内存消耗。可以将它们标记得越早进行清理越好。

更新

我回答这个问题已经5年了。我仍然同意我的回答。如果有一个dispose方法,则在处理完对象后应调用它。之所以实现IDispose接口是有原因的。


5
调用dispose并不能加快内存的回收速度,为此,您必须手动启动垃圾收集器,这通常是一个不好的计划。
Tetraneutron

2
如果Dispose将一堆引用设置为null,则可能导致对象成为收集的候选对象,否则可能会被跳过。
格雷格·迪恩

1
Dispose的目的不是清除托管对象的内存-这是垃圾收集器的工作。重点是清除非托管对象。似乎有证据表明,DataSet没有任何非托管引用,因此从理论上讲,不需要处置它们。话虽这么说,但我从来没有遇到过必须竭尽全力打电话给Dispose的情况-无论如何我还是会打电话给它。
cbp

4
IDisposable 的主要用途是释放非托管资源。通常,它还以对已处置实例有意义的方式修改状态。(即属性设置为false,引用设置为null等)
Greg Dean 2009年

3
如果对象上有dispose方法,则将其放置在该对象上是有原因的,无论是否用于清理非托管对象。
Chuck Conway 2012年

4

如果您的意图或此问题的上下文确实是垃圾回收,则可以将数据集和数据表显式设置为null或使用关键字using并使其超出范围。处置并不像Tetraneutron先前所说的那样重要。GC将收集不再引用的数据集对象以及超出范围的对象。

我真的希望如此,迫使人们在投票否决答案之前,投下反对票以实际发表评论。


+ 1我猜有些人不想让其他人考虑不同的观点。
DOK

2
否决票,绝不禁止人们考虑不同的观点。
格雷格·迪恩

1

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


6
也许现在,谁知道以后会做什么。
格雷格·迪恩

您以这种态度推测任何代码将来都不会做应该做的事情,这对于所有相关人员而言都是一种痛苦。
MicroservicesOnDDD


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.