210

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

但是,从我目前所读到的内容来看,DataSet 和 DataTable 实际上没有任何非托管资源,因此 Dispose() 实际上并没有做太多事情。

另外,我不能只使用using(DataSet myDataSet...),因为 DataSet 有一个 DataTables 集合。

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

那么,在我的所有数据集和数据表上调用 Dispose() 是否值得?

附录:

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

然而,这对于一个集合来说变得非常难看。例如,如果对 Dispose() 的调用之一引发异常,您会怎么做?你吞下它(这是“坏的”),以便你可以继续处理下一个元素吗?

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

4

12 回答 12

158

这里有几个讨论解释了为什么 DataSet 不需要 Dispose。

处置还是不处置?

DataSet 中的 Dispose 方法的存在只是因为继承的副作用——换句话说,它实际上并没有在终结中做任何有用的事情。

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

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

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

在实践中,我们很少 Dispose 数据集,因为它提供的好处很少”

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

于 2009-05-26T23:29:07.663 回答
136

更新(2009 年 12 月 1 日):

我想修改这个答案并承认原来的答案是有缺陷的。

最初的分析确实适用于需要最终确定的对象——没有准确、深入的理解,不应该在表面上接受实践的观点仍然存在。

然而,事实证明 DataSets、DataViews、DataTables在它们的构造函数中抑制了终结——这就是为什么在它们上显式调用 Dispose() 什么都不做的原因。

据推测,这是因为它们没有非托管资源。因此,尽管MarshalByValueComponent允许使用非托管资源,但这些特定的实现不需要,因此可以放弃最终确定。

(.NET 作者会注意抑制通常占用最多内存的类型的终结性,这说明了这种做法对于可终结类型的重要性。)

尽管如此,自从 .NET Framework (大约 8 年前)问世以来,这些细节仍然没有得到充分的记录,这非常令人惊讶(您基本上只能依靠自己的设备来筛选相互冲突、模棱两可的材料来将各个部分组合在一起有时令人沮丧,但确实提供了对我们每天所依赖的框架的更完整的理解)。

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

如果一个对象需要终结,它占用的内存可能比它需要的时间长——原因如下: a) 任何定义析构函数(或从定义析构函数的类型继承)的类型都被认为是可终结的;b) 在分配时(在构造函数运行之前),一个指针被放置在终结队列上;c) 一个可终结的对象通常需要回收2 个集合(而不是标准的 1 个);d) 抑制终结不会从终结队列中删除对象(如 SOS 中的 !FinalizeQueue 所报告的) 此命令具有误导性;知道最终队列中的对象(本身)是没有帮助的;知道什么对象在终结队列上并且仍然需要终结将是有帮助的(有这个命令吗?)

抑制终结会在对象的标头中关闭一点,向运行时指示它不需要调用其终结器(不需要移动 FReachable 队列);它保留在 Finalization 队列中(并继续由 SOS 中的 !FinalizeQueue 报告)

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

  • 因为 DataTable、DataSet、DataView 不引入非托管资源,它们在构造函数中抑制了终结
  • 虽然这是一种不寻常的模式,但它使调用者不必担心在使用后调用 Dispose
  • 这一点,以及 DataTables 可能在不同的 DataSets 之间共享这一事实,可能是 DataSets 不关心处置子 DataTables 的原因
  • 这也意味着这些对象会出现在 SOS 中的 !FinalizeQueue 下
  • 但是,这些对象在单次收集后仍应可回收,就像它们的不可终结对象一样

4(新参考):

原答案:

对此有很多误导性且通常非常糟糕的答案 - 任何来到这里的人都应该忽略噪音并仔细阅读下面的参考资料。

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

数据表是可终结的。

调用 Dispose显着加快了内存的回收。

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

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

首先,需要终结的对象比不需要终结的对象寿命更长。事实上,他们可以活得更久。例如,假设需要完成 gen2 中的对象。将安排完成,但对象仍在 gen2 中,因此在下一次 gen2 收集发生之前不会重新收集它。这确实可能是一个很长的时间,事实上,如果事情进展顺利,这将是很长一段时间,因为 gen2 收集成本很高,因此我们希望它们很少发生。需要完成的旧对象可能必须等待数十个甚至数百个 gen0 集合才能回收它们的空间。

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

最后,需要终结的对象为终结器线程创建工作。如果您的终结过程是一个复杂的过程,那么唯一的终结器线程将花费大量时间执行这些步骤,这可能会导致工作积压,从而导致更多对象等待终结。因此,终结者尽可能少地做工作是至关重要的。还要记住,虽然所有对象指针在终结期间都保持有效,但这些指针可能会指向已经终结的对象,因此可能不太有用。即使指针是有效的,在终结代码中避免跟随对象指针通常是最安全的。安全、简短的最终确定代码路径是最好的。

从在 Gen2 中看到 100 MB 的非引用数据表的人那里得到它:这非常重要,并且完全被这个线程上的答案所忽略。

参考:

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/

于 2009-10-21T20:47:36.420 回答
27

您应该假设它做了一些有用的事情并调用 Dispose,即使它在当前的 .NET Framework 版本中什么都不做。无法保证它会在未来的版本中保持这种状态,从而导致资源使用效率低下。

于 2009-05-26T23:25:02.960 回答
18

即使一个对象没有非托管资源,disposing 也可能通过破坏对象图来帮助 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);
                }
            }
        }
    }
}
于 2009-05-27T08:39:05.263 回答
8

您自己创建数据表吗?因为通常不需要遍历任何对象的子对象(如在 DataSet.Tables 中),因为处理其所有子成员是父对象的工作。

通常,规则是:如果您创建了它并且它实现了 IDisposable,则 Dispose 它。如果您没有创建它,则不要处置它,这是父对象的工作。但是每个对象可能都有特殊的规则,请查看文档。

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

于 2009-05-27T08:49:48.627 回答
7

每当对象实现 IDisposeable 时,我都会调用 dispose。它的存在是有原因的。

数据集可能会占用大量内存。越早标记它们进行清理越好。

更新

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

于 2009-05-26T23:19:21.177 回答
5

如果您的意图或这个问题的上下文确实是垃圾收集,那么您可以将数据集和数据表显式设置为 null 或使用关键字 using 并让它们超出范围。Dispose 并没有像 Tetraneutron 之前所说的那样做太多事情。GC 将收集不再引用的数据集对象以及超出范围的对象。

我真的希望 SO 迫使人们在拒绝投票之前实际写评论。

于 2009-05-26T23:34:55.360 回答
1

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

于 2009-05-26T23:18:25.017 回答
0

尝试使用 Clear() 函数。它非常适合我处理。

DataTable dt = GetDataSchema();
//populate dt, do whatever...
dt.Clear();
于 2013-11-20T16:49:15.097 回答
0

这是正确处置DataTable.

private DataTable CreateSchema_Table()
{
    DataTable td = null;
    try
    {
        td = new DataTable();
        //use table DataTable here
        
        return td.Copy();
    }
    catch {  }
    finally
    {
        if (td != null)
        {
            td.Constraints.Clear();
            td.Clear();
            td.Dispose();
            td = null;
        }
    }
}
于 2020-09-30T17:31:29.263 回答
0

不需要 Dispose() 因为 DataSet 继承 MarshalByValueComponent 类和 MarshalByValueComponent 实现 IDisposable 接口

于 2018-10-11T12:51:45.930 回答
0

这可能是处置和释放DataSet.

try
    {
        DataSet ds = new DataSet("DS");
        //use table DataTable here
        
    }
    catch {  }
    finally
    {
        if (ds != null)
                {
                    ds.EnforceConstraints = false;
                    ds.Relations.Clear();
                    int totalCount = ds.Tables.Count;

                    for (int i = totalCount - 1; i >= 0; i--)
                    {
                        DataTable td1 = ds.Tables[i];
                        if (td1 != null)
                        {
                            td1.Constraints.Clear();
                            td1.Clear();
                            td1.Dispose();
                            td1 = null;
                        }
                    }

                    ds.Tables.Clear();
                    ds.Dispose();
                    ds = null;
                }
    }
于 2020-09-30T17:40:25.693 回答