首页 > 解决方案 > IDisposable 和私有 bool 已处理。这个怎么运作?

问题描述

对不起,我之前问了一个类似的问题,并且有答案。但是我又一次遇到了这个话题,这让我大吃一惊。所以,John Sharp 的书,Microsoft Visual C# Step by Step 9ed。第 2 部分,第 14 章。

引用:

class Example : IDisposable
    {
        private Resource scarce;       // scarce resource to manage and dispose
        private bool disposed = false; // flag to indicate whether the resource
                                       // has already been disposed 
        ~Example()
        {
            this.Dispose(false);
        }
    
        public virtual void Dispose()
        {
            this.Dispose(true);
            GC.SuppressFinalize(this);
        }
    
        protected virtual void Dispose(bool disposing)
        {
            if (!this.disposed)
            {
                if (disposing)
                {
                    // release large, managed resource here                
                }
                // release unmanaged resources here            
                this.disposed = true;
            }
        }
    
        public void SomeBehavior() // example method
        {
            checkIfDisposed();        
        }
        
    
        private void checkIfDisposed()
        {
            if (this.disposed)
            {
                throw new ObjectDisposedException('Example: object has been disposed');
            }
        }
    }

请注意 Example 类的以下特性:

  • 该类实现 IDisposable 接口。

  • 您的应用程序代码可以随时调用公共 Dispose 方法。

  • 公共 Dispose 方法调用 Dispose 方法的受保护和重载版本,该方法采用布尔参数,将值 true 作为参数传递。该方法实际上执行了资源处置。

  • 析构函数调用 Dispose 方法的受保护和重载版本,该方法采用布尔参数,将值 false 作为参数传递。析构函数仅在您的对象完成时由垃圾收集器调用。

  • 您可以多次安全地调用受保护的 Dispose 方法。变量disposed指示该方法是否已经运行,并且是一个安全特性,用于防止该方法在同时调用时多次尝试释放资源。(您的应用程序可能会调用 Dispose,但在该方法完成之前,您的对象可能会受到垃圾回收,并且由 CLR 从析构函数再次运行 Dispose 方法。)资源仅在该方法第一次运行时被释放。

  • 受保护的 Dispose 方法支持托管资源(例如大型数组)和非托管资源(例如文件句柄)的处置。如果 disposing 参数为 true,则必须从公共 Dispose 方法调用此方法。在这种情况下,托管资源和非托管资源都被释放。如果 disposing 参数为 false,则必须从析构函数调用此方法,并且垃圾收集器正在终结对象。在这种情况下,没有必要(或异常安全)释放托管资源,因为它们将或可能已经由垃圾收集器处理,因此仅释放非托管资源。

  • 公共 Dispose 方法调用静态 GC.SuppressFinalize 方法。此方法阻止垃圾收集器调用此对象的析构函数,因为该对象已经完成。

  • 该类的所有常规方法(例如 SomeBehavior)都会检查对象是否已被丢弃。如果有,它们会抛出异常。

我用粗体突出了一段我不明白在什么情况下这是可能的。特别是这个:

您的应用程序可能会调用 Dispose,但在该方法完成之前,您的对象可能会受到垃圾回收的影响,并且 Dispose 方法会由 CLR 从析构函数再次运行。

我不明白。如果 Dispose 方法还没有完成,那么 this.disposed = true 的操作还没有执行,那么 this.disposed 仍然是 false。当 this.Dispose (true) 运行时,对象将被垃圾回收。我已经接受了这个 - 方法仍然有效,在仍然有效的方法中有使用 this 关键字的操作,也就是说,我们仍然在使用这个对象,有使用这个对象的成员的操作(this .disposed = true),并且当方法 (this.Dispose (true)) 完成后,GC.SuppressFinalize (this) 代码应该可以工作,其中也包含 this 关键字,但由于某种原因,我们的对象仍然是主题到书中写的垃圾收集。

好的。让我们假设。

但是 this.disposed 仍然是 false,当垃圾回收并且从析构函数中调用 this.Dispose(false) 方法时, this.disposed 字段将不起作用。

作者有错吗?为什么要写disposed field的作用,并举例说明这个field没用?还是我失去理智了?

标签: c#.net

解决方案


GC 只会在确定对象的任何实例成员都不会被代码再次调用时才会释放对象的内存。因此,虽然引用是正确的,对象可能在Dispose完成执行之前被收集,但只有当当前执行的代码没有可能到达实例变量的代码路径时,这才是正确的。因此,在您的特定实现中,直到已经运行之后才能释放对象的内存this.disposed = true;(假设没有任何可执行代码的其他可能方法可以访问另一个实例变量)。

因此,该断言的全部意义在于告诉您,您要么需要在清理完成之前对被清除的实例感到满意,要么您需要disposed在任何预期的操作之后访问某个实例成员(通过设置来实现)实例仍然存在。

也就是说,这些观点通常都相当深奥,很少出现。如果您从不访问任何实例成员,您甚至不可能意识到该对象的内存在其他一些未管理的处置完成之前在技术上已被释放。如果您正在积极尝试观察效果,​​您可能不得不使用弱引用或持有对该实例状态的引用的不安全代码之类的东西(这正是您应该尽可能避免这样做的原因)。


推荐阅读