仍然试图了解处置模式

时间:2010-08-03 16:26:44

标签: c# dispose

我已经阅读了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

}

结果是:

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

测试1b - 取消注释析构函数,可以在关闭应用程序时调用所有处理。

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

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

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

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

提前谢谢

斯特凡诺

6 个答案:

答案 0 :(得分:10)

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

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

  1. 正在包装本机资源。 (在这种情况下,您可能还需要终结器。)
  2. 正在封装实现IDisposable的类。 (在这种情况下,您需要IDisposable,但不需要实现终结器/析构函数。)
  3. 在我看来,与IDisposable混淆的部分原因是它涵盖了很多用例,并且正确的实现会根据您正在做的事情而改变(即:包装本机资源,包装其他IDisposable)类,或使用Factored类型)。为了解决这个问题,我写了multiple part series on IDisposable - 它可能有助于为你澄清一些内容。

答案 1 :(得分:1)

正确的模式是当您的类包含非托管资源时才使用终结器。至于依靠GC处理您的托管对象,请不要这样做。 IDisposable合同明确表明需要处理此对象。

答案 2 :(得分:1)

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

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

谢谢大家的答案和例子

答案 3 :(得分:1)

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

你需要再看看。理由当析构函数(也就是终结器)运行时会发生什么。 disposing 参数将为false,受保护的Dispose方法不执行任何操作。这是完全正常的,终结者应该只释放非托管资源。你没有。在您自己的代码中拥有非托管资源是非常罕见的。它们属于.NET中可用的优秀包装类,可将非托管操作资源转换为良好的托管类。如果你发现自己认为自己需要终结者,那么99.99%的时间你都会错。即使您确实包装了非托管资源,也应该使用其中一个SafeHandle包装器。并依靠他们的终结者。

好的,你想要摆脱析构函数。保留它是不健康的,它会使对象在内存中保持的时间超过必要的时间。当你这样做时,你会把它减少到:

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

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

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

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

答案 4 :(得分:1)

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

“官方”模式不必要地复杂且不必要地混淆。

答案 5 :(得分:0)

IDisposable的主要目的是拥有一个可以处理非托管资源的一致标准接口,也就是说,如果你没有采取措施确保Dispose()被调用,这些资源会在应用程序关闭后徘徊。 using()语法也理解,即使用将为您实现以下块:

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

这一切都是在一个漂亮的设计中实现的:

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