首页 新闻 赞助 找找看

对 IDisposable接口的实现的疑惑

0
悬赏园豆:60 [已解决问题] 解决于 2012-10-24 11:55

这是MSDN的 接口说明

http://msdn.microsoft.com/zh-cn/library/system.idisposable.aspx

之前也提问过,现在重看,还是有一点迷糊。MSDN例子中的,资源释放代码如下

protected virtual void Dispose(bool disposing)
        {
            // Check to see if Dispose has already been called.
            if(!this.disposed)
            {
// 为什么要加这个判断
                if(disposing)
                {
                    // Dispose managed resources.
                    component.Dispose();
                }

                // Call the appropriate methods to clean up
                // unmanaged resources here.
                // If disposing is false,
                // only the following code is executed.
                CloseHandle(handle);
                handle = IntPtr.Zero;

                // Note disposing has been done.
                disposed = true;

            }
        }

这个方法中的中文注释那,为什么要加那个判断。

先谈我的疑惑:

如果用户忘记调用公用的 Dispose方法,也不用担心,因为有析构函数,GC到时会调用析构函数,析构函数会调用 Dispose(false);但是有了 if(disposing)  判断,那不是component的非托管资源无法释放? 看到很多数据库连接释放也是在这个if下,为什么?

 

Qlin的主页 Qlin | 老鸟四级 | 园豆:2403
提问于:2012-10-23 17:13
< >
分享
最佳答案
0

但是有了 if(disposing)  判断,那不是component的非托管资源无法释放?

不必担心,因为Component类也有它自己的析构器,Comonent的实例对象被GC时保证了它引用的非托管资源被释放。

那为什么要这么写呢?举个例子你就很容易明白:

class Inner : IDisposable 
{ 
      // 包含一些非托管资源 
      // 实现了 ~Inner() 和 去掉了if判断的 Dispose 方法
}


class Wrapper : IDisposable
{
      //这是包含了非托管资源的托管资源
      public Inner InnerResouce { get; set; }

      // 此外本身也有一些非托管资源
      // 实现了 ~Wrapper() 和 去掉了if判断的 Dispose 方法
}

class Test
{
      Wrapper wrapper = new Wrapper();

      void SomeMethod()
      {
           var inner = new Inner();
           Wrapper anotherWrapper = new Wrapper();

           wrapper.InnerResouce = inner;
           anotherWrapper.InnerResouce = inner;

           // 在这个方法结束后会发生什么?
      }
}

SomeMethod结束后的某个时刻,anotherWrapper被GC,调用了析构器,由于你去掉了if判断,导致Inner.Dispose也被调用了,直接导致wrapper这个实例里的Inner不能正常工作了,这显然不是我们想要的。

由此可见平时被忽略的基础的重要性,其实微软的代码实现只是很简单的遵循了一个很基础的大家都知道的原则,就是职责单一,即:析构器负责释放自身的非托管资源,Dispose方法负责释放所引用到的所有非托管资源,它们两者职责不同,调用时机也不同,Dispose方法永远是程序员显式调用(无论是你用a.Dispose()还是using块),都明确了调用时机。而析构器则无法确定调用时机,因此它没有权利去操纵它成员的非托管资源(因为它无法知道它的成员是否真的需要被释放)

其实你再细心一点,这里Dispose(bool disposing)这个方法还有一个很耐人寻味的地方,就是virtual关键字,这里就不多说了。。

收获园豆:30
水牛刀刀 | 大侠五级 |园豆:6350 | 2012-10-24 10:25

谢谢,有点明白了。

如果 Inner  和 Wrapper  都有一个 this.disposed 字段来判断是否已调用,就可以保证调用一次了。

那就 就可以 不用 if 判断了吧?

 

Qlin | 园豆:2403 (老鸟四级) | 2012-10-24 11:00

@Qlin: 不行啊,如果没有if判断,那么还是会有我上面说的那个问题,你加disposed判断怎么加?

水牛刀刀 | 园豆:6350 (大侠五级) | 2012-10-24 11:06

@水牛刀刀: 

protected virtual void Dispose(bool disposing)
        {
            // Check to see if Dispose has already been called.
            if(!this.disposed)
            {
// 为什么要加这个判断
                if(disposing)
                {
                    // Dispose managed resources.
                    component.Dispose();
                }

                // Call the appropriate methods to clean up
                // unmanaged resources here.
                // If disposing is false,
                // only the following code is executed.
                CloseHandle(handle);
                handle = IntPtr.Zero;

                // Note disposing has been done.
                disposed = true;

            }
        }

最外面那个if判断

Qlin | 园豆:2403 (老鸟四级) | 2012-10-24 11:11

@Qlin: 你贴的这个代码跟微软的实现有什么不同?这个就是标准的正确实现啊。

水牛刀刀 | 园豆:6350 (大侠五级) | 2012-10-24 11:24
其他回答(3)
0
// Dispose managed resources.
是你看错了吧,明明是说释放托管资源

不管disposing为true还是false,非托管资源都会被释放。
收获园豆:5
向往-SONG | 园豆:4853 (老鸟四级) | 2012-10-23 20:36

component是托管资源,但是它的component.Dispose();释放的是component里头的非托管资源。

如 文件流, FileStream stream= File.OpenRead("") ;打开了一个文件,要释放非托管资源必须调用Dispose方法,会关闭执行流。

现在我想问的是 为什么会 写在 if(true)下面stream.Dispose(),如果为真才释放的话,用户在没有调用Dispose方法时,那不是stream会一直占用那个文件,因为析构函数也不会调用。

支持(0) 反对(0) Qlin | 园豆:2403 (老鸟四级) | 2012-10-23 20:58

@Qlin: 

不调用Dispose是会一直占用着啊,所以才需要用using包着或调用close。

支持(0) 反对(0) 向往-SONG | 园豆:4853 (老鸟四级) | 2012-10-23 21:08

@向往-SONG: 

那 为什么不放在 if(true)外面呢?

支持(0) 反对(0) Qlin | 园豆:2403 (老鸟四级) | 2012-10-23 21:38
0

看到你之前问过类似的问题。

你这里说的非托管资源,即文件资源,是存在这个叫handle的句柄,这里释放的逻辑是在CloseHandle里面去释放的文件资源。

至于要说component中dispose所释放的资源,那是取决于component里面是否有非托管资源需要释放,因为你在外面是不可知的,只有component对象自己最清楚。假设它有使用非托管资源,dispose里面自然就会调用相应的native api去释放资源,如果没有,则可能只是一些实例变量或者静态变量的null化过程。

 

这里为什么当finalize方法调用的时候传参数为false? 是因为gc在调用对象的finalize方法是没有一定的顺序的,这个过程可能随机的,也就是说,如果该代码是在finalize中调用的时候,不保证component对象的finalize方法是否被调用过(也就不能保证component对象是否存在,这话讲错了,sorry。然后你担心的在component里面的非托管资源会在它自身的finalize方法中被释放。

收获园豆:15
Ethan轻叹 | 园豆:996 (小虾三级) | 2012-10-23 21:23

不是说 component的finalize方法启动释放。component里的确是有非托管资源,

我是想 使用MyResource类时,不管用户有没有调用MyResource的dispose方法,都可以达到释放MyResource里的非托管资源。但是 放在 if(true)里就不会保证释放了,必须用户调用了。

不只是component,看其他的经典用法都是类似,文件资源、数据库连接等等,都是放在if(true)下,为什么要加这个判断。应该不是 说这个对象不存在了,不存在 大可以判断 component是否等于null。

支持(0) 反对(0) Qlin | 园豆:2403 (老鸟四级) | 2012-10-23 21:53

@Qlin: 对象a本身的非托管资源的是必须会被释放的。而在finalize方法的时候,对象a所引用的对象b的非托管资源的释放是不应该在a里面调用触发释放的,而是应该被显式(using)或者隐式(finalize)释放的。所以才会有这个disposing开发的

支持(0) 反对(0) Ethan轻叹 | 园豆:996 (小虾三级) | 2012-10-23 22:00

@Ethan轻叹: 

  对象a所引用的对象b的非托管资源的释放是不应该在a里面调用触发释放的

a.Dispose()方法 不是显示触发释放了b的非托管资源吗? 这是在a中触发的。

为什么a.finalize()方法 不应该触发b的非托管资源,而a.Dispose()方法可以?

 

支持(0) 反对(0) Qlin | 园豆:2403 (老鸟四级) | 2012-10-24 08:34

@Qlin: a.Dispose()是调用了Dispose(true)的,对吧?而a的finalize方法调用的则是Dispose(false),对吧?所以调用a.Dispose的时候调用了b.Dispose,没问题。

 

当a的finalize被调用的时候,a及相关对象b的引用都被放置在了freachable queue了,并且在该queue里面的调用顺序也未可知,但是都等着被调用各自finalize方法,调用完之后就被从queue中移除,然后就被认为是垃圾了,等着被回收。

只能说是这样更能利用gc的垃圾回收机制而已吧。

 

 

 

支持(0) 反对(0) Ethan轻叹 | 园豆:996 (小虾三级) | 2012-10-24 09:40

@Ethan轻叹: 

你的 freachable queue 是什么?是终止化队列吗

a、bfinalize,a/b在初始化时,才会添加到终止化队列,终止化队列中有一个指针指向a\b对象。

当a对象成为垃圾了,GC才会去调用a的finalize方法,当然b也成为垃圾了,GC也会去调用b的finalize方法,此顺序不确定。

如果没有那个if判断,

假设a 先执行,同时执行了b的Dispose释放,到GC执行b的finalize时,会报错吗?

假设b的finalize先执行,到GC执行a的finalize时,去执行b的Dispose()会报错吗?

如果a\b 都有 disposed字段 就好了,都只会执行一次?

支持(0) 反对(0) Qlin | 园豆:2403 (老鸟四级) | 2012-10-24 10:50

@Qlin: 

freachable queue 不是终止化队列,所有finalize的对象都有一次复活的机会,复活后放到这个队列。

还有就是dispose方法应该被实现成可安全的重复调用,这是约定吧,理论上无论在何时在调用时候都不应该报错的,除非对象不存在。

支持(0) 反对(0) Ethan轻叹 | 园豆:996 (小虾三级) | 2012-10-24 12:28
1

这个两个类,一个是当前类本身,另外一个是component类,第一个if判断是自身是否需要释放资源,第二个if判断是component类是否需要释放资源,这样做的目的是判断这个component是否被当前类使用,如果正在使用中就不释放这个资源,只释放类本身的资源。

收获园豆:10
az235 | 园豆:8483 (大侠五级) | 2012-10-24 11:07

那 当调用了 析构函数时,这个MyResource对象已是垃圾,它里面的component对象当然也是垃圾了,

还用得着 判断吗?当然是 不需要使用component,应该要释放的啊

支持(0) 反对(0) Qlin | 园豆:2403 (老鸟四级) | 2012-10-24 11:16

@Qlin: “这个MyResource对象已是垃圾,它里面的component对象当然也是垃圾了”,不对啊,你没看懂我的例子吗

支持(0) 反对(0) 水牛刀刀 | 园豆:6350 (大侠五级) | 2012-10-24 11:24

@水牛刀刀:

哦,有两个引用 谢谢

支持(0) 反对(0) Qlin | 园豆:2403 (老鸟四级) | 2012-10-24 11:30
清除回答草稿
   您需要登录以后才能回答,未注册用户请先注册