为什么使用Dispose作为常规方法是不好的

本文关键字:方法 常规 Dispose 为什么 | 更新日期: 2023-09-27 18:18:32

试着弄清楚。有人告诉我

Dispose是而不是只是一个方法——它相当于其他语言中的析构函数。

Ok。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的意思是"问题不好/没用/有问题"。如果你只是不同意我的想法-发表评论或回答。它将对那些像我现在这样思考的人有用(因为我错了?

为什么使用Dispose作为常规方法是不好的

Dispose只是一个方法,你可以像调用其他方法一样调用它。它总是通过IDisposable接口暴露(是的,显然您可以将方法命名为Dispose而不实现IDisposable不要这样做!)。然而,手动调用有时是一种代码气味,可能应该使用using代替的代码气味。这里的"手动"是指在另一个Dispose的实现之外调用Dispose

调用它两次也应该是安全的,并且有文档记录:

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

(我强调)

你应该调用Dispose两次吗?不! 。这也是一种代码的味道,代码不再能够控制它已经做了什么,还有什么要做,最终做的事情"只是为了确定"。也不要这样做!

所以如果你写的代码正确,当然你可以手动调用Dispose

你是对的,Dispose只是另一个方法,属于IDisposable。它还有一个额外的好处,就是能够在using()作用域结束时自动调用——它不是等同于析构函数,告诉你这一点的人并不真正理解他们在说什么。

我总是在可能的情况下使用using()块,但如果你小心的话,你可以手动调用Dispose来代替,它会有同样的效果。

Visual Studio 2015提供了(最终)一个符合指南的IDisposable实现建议模式。

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

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"的方法。

. net中的Dispose与其他语言中的析构函数之间有一个关键的区别:如果一个人有一个指向另一种语言中的对象的指针,并且该对象被删除了,那么一个人将拥有一个无效的指针,并且没有办法对它做任何事情,包括确定它是否仍然有效,而不调用未定义行为。相比之下,在。net中,如果一个对象的引用获得了 Disposed,那么该引用将继续是对该对象的有效引用。对象可能会拒绝做很多事情,因为它已经被处理了,但这种拒绝是由对象本身肯定地产生的。无未定义行为。

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