【问题标题】:still trying to understand the dispose pattern仍在尝试了解处置模式
【发布时间】:2011-03-24 20:04:44
【问题描述】:

我已经阅读了 msdn 和有关 dispose 模式的各种帖子,但仍有一些我不明白的地方。我编写了以下代码来测试处置模式。请注意,没有未管理的资源,我使用的是 vs2008 和 .net 3.5:

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
    }

    private void tryDispose()
    {
        //test 1 : allocate resource and leave manage it to gc
        BL.myclass df = new BL.myclass();
        //test 2 : try to force garbage collecting
        //GC.Collect();
       //test 3 : call dispose myself
       //using (BL.myclass df = new BL.myclass())
        //{ 

        //}

    }

    private void button1_Click(object sender, EventArgs e)
    {
        tryDispose();
    }

这是我的一次性课程:

class myclass: IDisposable
{

    private StronglyTypedDs myDS;
    private bool _disposed;

    public myclass()
    {
        using (myDSTableAdapter docDocadpt = new myDSTableAdapter())
        {
            myDS = new StronglyTypedDs();
            docDocadpt.Fill(myDS.TheTable);
        }
    }


    #region IDisposable Members

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    ~myclass()
    {
        Dispose(false);
    }

    protected virtual void Dispose(bool disposing)
    {
        if (!_disposed)
        {
            if (disposing)
            {
                if (myDS != null)
                    myDS .Dispose();
                myDS = null;
            }
        }
        _disposed = true;
    }


    #endregion

}

结果是:

test 1a - 只是实例化 myclass,析构函数是 commentend,因为 myclass 不包含非托管资源:myclass.dispose 不会被调用,即使我关闭了应用程序(它的 dispose 被执行)。那么一旦我关闭应用程序,数据集的状态是什么?

test 1b - 取消注释析构函数,当我关闭应用程序时,所有的 dispose 都会被调用。

测试 2a 和 2b - 我只调用 gc.collect 进行上述测试:行为分别与测试 1a 和 1b 相同

测试 3 - 一切正常(当然)

很多帖子说,如果 myclass 不包含非托管资源,我不需要添加析构函数;然而在我的测试中,如果我不添加析构函数,当我关闭应用程序时 myclass.dispose 不会被调用。我还没有等待 gc 自己运行(如果我没记错的话 gc.collect 不保证类实例被释放)来检查它是否会调用 myclass.dispose 。 那么正确的实现是什么:总是定义 e 析构函数,或者如果 myclass 仅包含托管资源则避免它? 如果我填满了所有世代级别,在没有实现析构函数的情况下,是否会调用 myclass dispose 的 gc?

最后我注意到,如果我定义了一个析构函数,但不将该类声明为实现 IDisposable,那么处置链仍然有效。这可能是有道理的,因为析构函数可能会被转换为在 IL 中完成。但我发现它真的很令人困惑:它是某种我不知道的“隐式”接口实现吗? gc 可以处理该项目,但用户不能

提前谢谢你

斯特凡诺

【问题讨论】:

  • 我只写过少数带有终结器的类(看起来像 C++ 析构函数的语法),这些类是用 C++/CLI 编写的,以实现与非托管 API 的互操作性。除非您与本机代码和资源进行大量互操作,否则您很少需要编写终结器。
  • @Brian:最常见的情况是间接处理资源,比如这里。
  • "如果我不添加析构函数,myclass.dispose 就不会被调用" - 但是析构函数使用 disposing=false 调用它,然后它什么都不做。走小路。仅当您直接处理非托管资源时,才使用析构函数(也称为终结器)。
  • @Henk:是的,你是对的。 :::删除他的评论以避免任何人可能不小心读到它并认为它是真的:::

标签: c# dispose


【解决方案1】:

您的代码是正确的,您已经实现了它完全,就像它记录在 MSDN 库中一样。

不过,您需要再看一遍。解释当析构函数(又名终结器)运行时会发生什么。 disposing 参数将为 false,受保护的 Dispose 方法什么都不做。这是完全正常的,终结器应该只释放非托管资源。你没有。在您自己的代码中很少有非托管资源。它们属于 .NET 中可用的漂亮包装类,用于将非托管操作资源转换为漂亮的托管类。如果你发现自己认为你需要一个终结器,那么 99.99% 的情况你都会错。即使您确实包装了非托管资源,也应该使用 SafeHandle 包装器之一。并依赖他们的终结器。

好的,你想摆脱析构函数。把它留在里面是不健康的,它使对象在内存中的时间超过了必要的时间。当你这样做时,你会将其缩减为:

public void Dispose()
{
    if (myDS != null) myDS.Dispose();
}

这是大多数 Dispose() 方法的样板实现,只需调用类成员的 Dispose 方法。如果您没有任何具有 Dispose() 方法的成员,则可以完全省略它。

接下来,您确实误解了 Dispose() 方法是如何被调用的。它不是自动的。这是首先拥有它的重点,自动释放资源已经由垃圾收集器处理。 Dispose() 方法可供 you 调用,可以使用 using 语句或直接调用它。这样您就可以提前释放资源,而不是等待垃圾收集器终结器线程处理它。这可能需要一段时间。当您知道您的程序将不再使用该对象时调用它。

如果表单正在积极使用您的 DataSet,那么在表单关闭之前,您无法释放它。在 FormClosed 事件处理程序中调用类的 Dispose() 方法。或者,更好的是,打开表单的 Designer.cs 文件,剪切并粘贴您在其中找到的 Dispose() 方法,然后将其移动到表单的源代码文件中。并添加 dispose 调用。我知道这有点令人困惑,但唯一一次可以编辑设计器文件。

【讨论】:

    【解决方案2】:

    我写了一篇题为How to Implement IDisposable and Finalizers: 3 Easy Rules的简短系列。它描述了自 BCL 2.0 版本以来,Microsoft 自己采用的一种更简单的方法。

    “官方”模式是不必要的复杂和不必要的混乱。

    【讨论】:

      【解决方案3】:

      好的,我想我已经理解了,参考我的示例,实现 dispose 是正确的,因为数据集对我的类是全局的并且实现了 IDisposable ,而我不需要终结器,因为没有非托管资源。 即使我“忘记”在我的类 dispose 方法中处理一些托管资源,gc 也会在某个时候收集它。 dispose 方法只是我为其他类/开发人员提供的用于托管资源的实用程序,如果我包装非托管资源,则必须使用终结器。

      我会尽快阅读您提供的文章,但同时我还有最后一个问题:gc 何时会释放我的班级及其资源拥有的内存?当有人调用 dispose 或它何时运行(它会释放内存而不是将其移至下一代)?

      谢谢大家的回答和例子

      【讨论】:

        【解决方案4】:

        相信你的垃圾收集器。托管资源最终将得到清理。除非您有一些需要释放的外部资源,否则不需要终结器或实现 IDisposable。

        这通常意味着您只应在以下情况下实现 IDisposable:

        1. 正在包装本机资源。 (在这种情况下,您可能还需要一个终结器。)
        2. 正在封装一个实现IDisposable 的类。 (在这种情况下,您需要IDisposable,但不需要实现终结器/析构函数。)

        在我看来,与IDisposable 的部分混淆在于它涵盖了相当多的用例,并且正确的实现会根据您正在做的事情而改变(即:包装本机资源,包装其他 IDisposable 类,或使用分解类型)。为了解决这个问题,我写了一个multiple part series on IDisposable - 它可能会帮助你澄清一些问题。

        【讨论】:

        • +1,但我对第 1 项有一个澄清。我会将其更改为“您正在包装 original 本机资源。”即:对于包装类似 SqlConnection 的类,您不需要终结器,即使 SqlConnection 包含本机资源,因为您不是该资源的原始包装器。 SqlConnection 已经为您服务了。
        • @Joel:这是第二个(封装一个实现 IDisposable 的类);)
        • 我知道,但是人们对他们可能同时执行 1 和 2 感到困惑,因此他们不确定。所以我用 #1 来确保 #2 被排除在外。
        • @Reed Copsey:我不喜欢使用“原生资源”这个词,因为按照该术语的任何正常定义,需要清理的东西可能不是“原生资源”,甚至可能不是在运行代码的计算机中!我认为从以下方面思考更有帮助:(1)这个对象在某个地方操纵了一些可能比它更长寿的东西; (2) 这种操纵如果不撤消可能是有害的; (3) 此对象是唯一可以撤消它的对象。请注意,包装的“资源”不会满足约束 #3,因为包装对象将能够处理“撤消”。
        • @Reed Copsey:对于 IDisposable,我认为“这个对象知道已经完成的事情应该尽快安全地撤消(但不是之前),并且这个对象会照顾一旦被告知要撤消它们。请让它知道何时应该清理“。如果一个对象拥有其他实现 IDisposable 的对象,并且被告知要清理,它有责任让其他对象知道它们也应该清理。
        【解决方案5】:

        IDisposable 的主要目的是拥有一个一致的标准接口,您可以使用它来处理非托管资源,也就是说,如果您没有做任何事情来确保调用 Dispose(),这些资源将在应用程序关闭后挂起。 using() 语法也可以理解,即 using 将为您实现以下块:

        DisposableType someDisposable = new DisposableType();
        try
        {
            // Do whatever
        }
        finally
        {
            ((IDisposable)someDisposable).Dispose();
        }
        

        这一切都是在一个很好的设计中实现的:

        using(DisposableType someDisposable = new DisposableType())
        {
            // Do whatever
        }
        

        【讨论】:

          【解决方案6】:

          正确的模式是仅当您的类包含非托管资源时才使用终结器。至于依靠GC 来处理您的托管对象,请不要。 IDisposable 合约清楚地表明该对象需要被释放。

          【讨论】:

          • 另外,如果对象不是Disposed,但没有任何非托管资源,那么原则上这不应该是致命的。它引用的任何托管类最终都应被收集,如果这些类具有非托管资源,则应在这些类最终确定时清理它们。也就是说,如果类是 IDisposable,则显式调用 Dispose要好得多,因为您可以让非托管资源闲置一段不确定的时间,直到终结器最终开始释放它们。
          • @Dan - 同意,这可能不是致命的,但肯定是个好主意。
          • @Dan - 如果使用 RAII,则需要调用 Dispose 以确保正确性。一个著名的例子是流作家。如果为 RAII 类跳过 Dispose,则 resources 将被清理得很好,但结果可能是错误的。
          • @Stephen,这很好;处置并不总是纯粹是为了释放资源。
          猜你喜欢
          • 2020-01-04
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2021-09-21
          • 1970-01-01
          • 1970-01-01
          • 2013-07-12
          • 2012-09-25
          相关资源
          最近更新 更多