代码之家  ›  专栏  ›  技术社区  ›  Reto Meier

IDisposable、终结器和非托管资源的定义

  •  5
  • Reto Meier  · 技术社区  · 15 年前

    我想确保我对 IDisposable 是正确的,我还有点不太确定。

    不可分的 似乎有两个目的。

    1. 提供按需“关闭”托管对象的约定。
    2. 为释放托管对象所持有的“非托管资源”提供约定。

    我的困惑来自于确定哪些场景中有“非托管资源”。

    假设您使用的是微软提供的 不可分的 -实现(托管)类(例如,与数据库或套接字相关)。

    1. 你怎么知道它是否正在实现 不可分的 只是为了 1和2 上面?
    2. 您是否负责确保释放它可能在内部保留或不保留的非托管资源?您是否应该添加终结器(这是正确的机制?)调用InstanceOfMSSuppliedClass.Dispose()的类?
    8 回复  |  直到 14 年前
        1
  •  10
  •   womp    15 年前
    1. 您如何知道它是只为1实现IDisposable,还是 1和2以上?

    第一个问题的答案是“你不需要知道”。如果您使用的是第三方代码,那么在某种程度上您是它的仁慈——当您调用Dispose时,您必须相信它正确地处理了自己。如果你不确定或者你认为有一个bug,你可以尝试使用reflector()来分解它(如果可能的话),并检查它在做什么。

    1. 我是否负责确保非托管资源 不在内部保留被释放?应该 我正在添加终结器 正确的机制?)属于我自己的班级 那个电话 InstanceOfMSSuppliedClass.Dispose()?

    如果使用的是.NET 2.0或更高版本,则很少需要为类实现终结器。终结器增加了类的开销,通常只提供Dispose所需的功能。我愿意 highly recommend visiting this article 了解如何正确处理。在你的情况下,你想打电话给 instanceofMSSuppliedClass.Dispose() 在您自己的dispose()方法中。

    最后,对一个对象调用Dispose()是一个很好的实践,因为它明确地让GC知道您已经完成了对资源的处理,并且允许用户立即清理它,并且通过让其他程序员知道该对象已经完成了对资源的处理来间接地记录代码。但是,即使您忘记显式调用它,它最终也会在对象未绑定时发生(.net毕竟是一个托管平台)。仅当您的对象具有需要隐式清理的非托管资源(即,使用者可能忘记清理它,而这将是有问题的)时,才应实现终结器。

        2
  •  5
  •   Talljoe    15 年前

    您应该始终对实现IDisposable的对象调用Dispose(除非它们明确地告诉您“这是一个有用的约定,如ASP.NET MVC的htmlHelper.BeginInform”)。您可以使用“using”语句来简化这一过程。如果将类中的IDisposable作为成员字段的引用挂起,则应使用 Disposable Pattern 清理那些成员。如果运行像fxcop这样的静态分析工具,它也会告诉您相同的情况。

    你不应该试图对界面进行二次猜测。今天这个类可能不使用非托管资源,但是下一个版本呢?

        3
  •  1
  •   Will Eddins ianpoley    15 年前

    您对对象的内容不负责。Dispose()应该是透明的,并且释放它需要释放的内容。在那之后,你不必为此负责。

    非托管资源是像您在(托管)C++中创建的资源,在这里,您通过指针和“新”语句分配内存,而不是“GCNEW”语句。当你在C++中创建一个类时,你有责任删除这个内存,因为它是本地内存,或者是非托管的,垃圾回收器不关心它。您还可以通过marshal分配来创建这个非托管内存,并且,我假定,还可以创建不安全的代码。

    当使用托管C++时,您也不必手动实现IDISPOSIGLE类。当编写解构函数时,它将编译为Dispose()函数。

        4
  •  1
  •   Zensar    15 年前

    如果所讨论的类是由Microsoft提供的(即。数据库等),那么对Dispose的处理(来自IDisposable)很可能已经处理好了,您可以调用它。例如,使用数据库的标准做法如下:

    //...
    using (IDataReader dataRead = new DataReaderObject())
    {
       //Call database
    }
    

    这与写作基本相同:

    IDataReader dataRead = null;
    try
    {
        dataRead = new DataReaderObject()
        //Call database
    }
    finally
    {
        if(dataRead != null)
        {
            dataRead.Dispose();
        }
    }
    

    据我所知,在从IDisposable继承的对象上使用前者通常是一种良好的做法,因为它将确保适当释放资源。

    至于自己使用IDisposable,实现取决于您自己。从中继承后,应确保该方法包含处理手动创建的任何DB连接所需的代码,释放可能保留或阻止销毁对象的资源,或者只清理大型资源池(如图像)。这还包括非托管资源,例如,标记在“不安全”块内的代码本质上是非托管代码,它可以允许直接内存操作,这绝对需要清理。

        5
  •  1
  •   supercat    14 年前

    术语“非托管资源”有些用词不当。基本概念是成对操作的概念——执行一个操作就需要执行一些清理操作。打开一个文件就需要关闭它。拨号调制解调器需要挂断。系统可能在执行清理操作失败后仍然存在,但后果可能很严重。

    当一个对象被称为“保留非托管资源”时,真正的意思是该对象具有对某些对象执行某些必需的清理操作所必需的信息和动力。 其他 实体,没有特别的理由相信信息和动力存在于任何其他地方。如果唯一拥有这些信息和动力的对象被完全放弃,所需的清理操作将永远不会发生。.dispose的目的是强制对象执行任何必需的清理,以便安全地放弃它。

    为了防止代码在不首先调用Dispose的情况下放弃对象,系统允许类注册为“Finalization”。如果已注册类的对象被放弃,系统将给该对象一个机会,在永久放弃之前对其他实体执行清除操作。但是,无法保证系统会在多快的时间内注意到某个对象已被放弃,并且各种情况可能会阻止该对象被提供清理的机会。术语“托管资源”有时用于指在放弃之前必须执行某些清理的对象,但如果有人未能调用Dispose,则该对象将自动注册并尝试执行此类清理。

        6
  •  0
  •   Jeroen Huinink    15 年前

    为什么这对你很重要?

    在可能的情况下,我将一次性物品的范围包装在使用中。这将在使用结束时调用Dispose。如果不需要,我会在不再需要对象时显式调用Dispose。

    无论是出于原因1还是原因2,都是不必要的。

        7
  •  0
  •   VVS    15 年前

    是的,你有责任打电话给 Dispose 方法-或更好的使用 using 语句。如果对象正在实现 IDisposable 不管怎样,你都应该把它处理掉。

    using (var myObj = new Whatever())
    {
       // ..
    }
    

    类似于

    {
      var myObj;
      try
      {
         myObj = new Whatever();
         // ..
      } 
      finally
      {
        if (myObj != null)
        {
          ((IDisposable)myObj).Dispose();
        }
      }
    } // object scope ends here
    

    编辑 :添加了Try/Finally感谢Talljoe-哇,这很难做到正确:)

    编辑2: 我不是说你应该使用第二种变体。我只是想证明“使用”对于一堆代码来说是一种很好的句法甜头,这些代码可能会变得非常混乱,很难纠正。

        8
  •  -1
  •   n8wrl    15 年前

    这里缺少的一个部分是终结器-我的习惯是,如果我实现IDisposable,我也有一个终结器来调用Dispose(),以防我的客户端不这样做。是的,它会增加开销,但是如果Dispose()被调用,而不是GC.SuppressFinalize(此)调用会消除它。

    推荐文章