为什么使用Dispose作为普通方法是不好的?

时间:2015-10-20 09:11:18

标签: c# language-lawyer dispose

试图搞清楚。有人告诉我

  

Dispose 只是一个方法 - 它等同于其他语言的析构函数。

确定。 Msdn对此也非常响亮。

但是

class Test : IDisposable
{
    public string Property { get; set; } = "Test";
    public void Dispose() => Console.WriteLine("Disposed, very scary");
}

class Program
{
    static void Main(string[] args)
    {
        var test = new Test();
        test.Dispose();
        test.Property = "123";  // but it's disposed OMG! do not do this!
        test.Dispose();

        using (var another = new Test())
            for (int i = 0; i < 10; i++)
            {
                another.Dispose();
                GC.Collect(); // or what should I call to make it crash?
            }

        Console.ReadKey();
    }
}

没有问题。

我对Dispose的看法:

  • 这是一种正常的公共方法;
  • IDisposableusing结合使用可自动调用Dispose,仅此而已;
  • 如果对象状态得到适当维护,任何时候都可以在内部调用任何代码,这是完全没问题的。

如果我错了,请纠正我。

P.S:Downvote意味着“问题很糟糕/没有用/有问题”。如果你只是不同意我的想法 - 发表评论或回答。对于那些我现在这样想的人(因为我错了?然后证明它)会很有用。

4 个答案:

答案 0 :(得分:8)

Dispose只是一种方法,你可以像任何其他方法一样调用它。它始终通过IDisposable界面公开(是的,很明显,您可以在不实施Dispose的情况下命名方法IDisposable不要这样做!)。< / p>

但是,手动调用有时代码味道,应该使用using代码的气味。这里的“手动”是指在另一个Dispose的实现之外调用Dispose

调用它两次也应该是安全的documented

  

如果多次调用对象的Dispose方法,则该对象必须忽略第一个之后的所有调用。 如果多次调用Dispose方法,则该对象不得抛出异常。 Dispose之外的实例方法在资源已经处理时可能抛出ObjectDisposedException。

(我的重点)

你应该两次打电话给Dispose吗? 否!。这也是代码的代码味道,它不再能控制它所做的事情和它剩下的事情并最终做“只是为了确定”。 也不要这样做!

因此,如果您正确编写代码,请务必手动调用Dispose

答案 1 :(得分:3)

你是对的,Dispose只是另一种属于IDisposable的方法。它只是有一个额外的好处,当using()作用域结束时能够被自动调用 - 它等同于析构函数,而且谁告诉你并不真正理解它们&# 39;说再说。

我总是尽可能使用using()块,但是如果你小心的话可以手动调用Dispose,它会产生相同的效果。

答案 2 :(得分:1)

Visual Studio 2015提供(最终)实施IDisposable的建议模式,该模式符合指南。

这是VS在选择实现一次性模式时为您创建的存根。 TODO指导我们完成实施。

class Disposable : IDisposable
{
    #region IDisposable Support
    private bool disposedValue = false; // To detect redundant calls

    protected virtual void Dispose(bool disposing)
    {
        if (!disposedValue)
        {
            if (disposing)
            {
                // TODO: dispose managed state (managed objects).
            }

            // TODO: free unmanaged resources (unmanaged objects) and override a finalizer below.
            // TODO: set large fields to null.

            disposedValue = true;
        }
    }

    // TODO: override a finalizer only if Dispose(bool disposing) above has code to free unmanaged resources.
    // ~Disposable() {
    //   // Do not change this code. Put cleanup code in Dispose(bool disposing) above.
    //   Dispose(false);
    // }

    // This code added to correctly implement the disposable pattern.
    public void Dispose()
    {
        // Do not change this code. Put cleanup code in Dispose(bool disposing) above.
        Dispose(true);
        // TODO: uncomment the following line if the finalizer is overridden above.
        // GC.SuppressFinalize(this);
    }
    #endregion
}

请注意使用标志来指示是否已调用Dispose。此标志可用于在类实现中的方法调用和属性上抛出ObjectDisposedException实例。

Dispose的意图一直是清理非托管资源。但是,using关键字的语法糖(需要IDisposable)可以创建非常整洁的“上下文”模式类来管理资源访问,即使没有使用非托管资源也是如此。

像往常一样,清楚地记录用法和意图,你不会出错,但如果你没有实现界面,请避免使用名为'Dispose'的方法。

答案 3 :(得分:0)

.NET中的Dispose与其他语言中的析构函数之间存在严重差异:如果一个指针指向另一种语言的对象,并且该对象被删除,则其中一个无效指针,没有办法对它做任何事情,包括确定它是否仍然有效,而不调用未定义的行为。相比之下,在.NET中,如果有一个对Dispose d的对象的引用,则引用将继续是对所讨论对象的有效引用。该对象可能会拒绝做很多事情,因为它已被处置,但这种拒绝是由对象本身肯定地产生的。不涉及未定义的行为。

另一方面,当在其他语言(如C ++)中调用析构函数时,对象可以确定不再存在有效指针,但Dispose则不然。因此,代码应避免汇集面向公共的对象,而是让每个对新对象的请求都返回一个新对象(可能是池对象的包装器)。如果在包装器之外不存在对池化对象的引用,并且Dispose在将对象返回到池之前使该引用无效,则可以通过将池化对象封装在将再次保持的新包装器中来满足将来的对象请求。唯一的参考。