【问题标题】:C# disposable objectsC# 一次性对象
【发布时间】:2011-04-26 09:57:11
【问题描述】:

关于我应该如何处理IDisposable 对象序列有什么建议吗?

例如,我有一个构建IEnumerable<System.Drawing.Image> 序列的方法和 在某些时候,我需要手动处理这些对象,否则可能会导致一些泄漏。

现在,有没有办法将 Dispose() 调用绑定到垃圾收集器操作,因为我希望这些对象在其他代码部分不再可访问时立即处理? p>

**或者你可以给我一些其他的建议吗? **


一般来说,这似乎是同样的问题,例如,在没有共享指针的非托管C++中,你可以有一个方法:

SomeObject* AllocateAndConstruct();

如果您不使用代码合同或未在 cmets 中声明某些内容,则您无法确定何时处置它。

我猜一次性物品的情况也差不多,但我希望有一个合适的解决方案。

【问题讨论】:

  • GC 在不再可访问后不会立即收集垃圾。收集此类对象可能需要一段时间

标签: c# collections dispose disposable


【解决方案1】:

(来自问题)

现在有没有办法绑定 Dispose() 调用垃圾收集器 行动,因为我想要这些对象 就在他们被安置的那一刻 不再可以从其他代码访问 零件?

当您的对象超出范围/到达范围时,

GC 不会立即发生;它是不确定的。当 GC 看到它时,做任何其他事情(终结器尚未处理)已经没有意义了,因为为时已晚。

因此,诀窍是知道何时完成,然后自己致电Dispose()。在许多情况下,using 实现了这一点。例如,您可以编写一个实现IDisposable 并封装一组Images 的类 - 并使用using 包装您对该封装对象的使用。包装器上的Dispose() 可以Dispose() 保存所有图像。

using(var imageWrapper = GetImages()) {
    foreach(var image in imageWrapper) {
         ...
    }
    // etc
} // assume imageWrapper is something you write, which disposes the child items

但是,如果您在 UI 上显示数据,这会有点棘手。那里没有捷径;您必须跟踪完成每个图像的时间,或者接受不确定的最终确定。

【讨论】:

    【解决方案2】:

    如果您想确定地处置集合中的对象,您应该在每个对象上调用Dispose

    myImages.ToList().ForEach(image => image.Dispose());
    

    如果你不这样做,并且如果你的对象变得不可访问,GC 最终会运行并释放它们。

    现在,如果您不想手动编码Dispose 调用,您可以创建一个实现IDisposable 的包装类并通过using 语句使用它:

    using (myImages.AsDisposable()) { 
      // ... process the images
    }
    

    这是所需的“基础设施”:

    public class DisposableCollectionWrapper<D> : IDisposable
    where D : IDisposable {
    
      private readonly IEnumerable<D> _disposables;
    
      public DisposableCollectionWrapper(IEnumerable<D> disposables) {
        _disposables = disposables;
      }
    
      public void Dispose() {
        if (_disposables == null) return;
        foreach (var disposable in _disposables) {
          disposable.Dispose();
        }
      }
    
    }
    
    public static class CollectionExtensions {
    
      public static IDisposable AsDisposable<D>(this IEnumerable<D> self)
      where D : IDisposable {
        return new DisposableCollectionWrapper<D>(self);
      }
    
    }
    

    还请注意,这与您使用 C++ 描述的情况不同。在 C++ 中,如果你不delete 你的对象,你就有一个真正的内存泄漏。在 C# 中,如果您不处置对象,垃圾收集器最终会运行并清理它。

    【讨论】:

      【解决方案3】:

      您应该以一种您知道何时不再需要资源的方式来设计您的系统。在最坏的情况下,它们最终会在垃圾收集器到达时被丢弃,但 IDisposable 的重点是您可以更早地释放重要资源。

      这个“较早”由您来定义,例如,您可以在使用它们的窗口关闭时释放它们,或者当您的工作单元完成对它们执行的任何操作时。但在某些时候,某些对象应该“拥有”这些资源,因此应该知道何时不再需要它们。

      【讨论】:

        【解决方案4】:

        您可以使用“使用”块来确保 IDisposable 在块离开后立即被释放。编译器确实将这些块封装到 try - finally 语句中,以确保在离开块时无论如何都会调用 Dispose。

        通过使用终结器,可以使 GC 为那些以某种方式“错过”的对象调用 Dispose 方法。但是,实现终结器的成本更高,并且会降低垃圾收集效率 - 可能还会降低应用程序的整体性能。因此,如果可能的话,您应该尽量确保自己处理您的 IDisposables;确定性:

        public class Context : IDisposable {
        
            List<IDisposable> m_disposable = new List<IDisposable>();
            public void AddDisposable(IDisposable disposable) {
                m_disposable.Add(disposable); 
            }
        
            public void Dispose() {
                foreach (IDisposable disp in m_disposable)
                    disp.Dispose(); 
            }
        
            // the Context class is used that way: 
            static void Main(string[] args) {
        
                using (Context context = new Context()) {
                    // create your images here, add each to the context
                    context.AddDisposable(image); 
                    // add more objects here 
        
                } // <- leaving the scope will dispose the context
            }
        }
        

        通过使用一些巧妙的设计,将对象添加到上下文中的过程可能会变得更加容易。可以为创建方法提供上下文或通过静态单例发布它。这样,它也可用于子方法范围 - 无需传递对上下文的引用。通过利用该方案,甚至可以模拟像 f.e. 这样的人工析构函数功能。从 C++ 中知道。

        【讨论】:

          【解决方案5】:

          简洁的方法是创建您自己的实现 IDisposable 的通用集合类。当这个集合类被 Disposed() 时,询问每个元素是否实现了 IDisposed,如果是,则将其丢弃。

          示例(如果您不了解 IDisposable 模式,请查看其他地方)

          public class MyDisposableList<T> : List<T> : IDisposable
          {
              private bool disposed = false;
          
              ~MyDisposableList()
              {
                  Dispose(false);
              }
          
              public void Dispose()
              {
                  Dispose(true);
              }
          
              protected void Dispose(bool disposing)
              {
                  if (!disposed)
                  {
                      foreach (T myT in this)
                      {
                          IDisposable myDisposableT = myT as IDisposable;
                          if (myDisposableT != null)
                          {
                              myDisposableT.Dispose();
                          }
                          myT = null;
                      }
                      this.Clear();
                      this.TrimExcess();
                      disposed = true;
                  }
              }
              ...
          }
          

          用法:

          using (MyDisposableList<System.Drawing.Bitmap> myList = new ...)
          {
              // add bitmaps to the list (bitmaps are IDisposable)
              // use the elements in the list
          }
          

          using 语句的末尾会自动处理 myList,从而处理 myList 中的所有位图 顺便说一句:如果你从一个文件中加载了位图,但你忘记了 Dispose() 位图,你不知道什么时候可以删除该文件。

          【讨论】:

            【解决方案6】:

            如果您真的要立即处理这些对象,您可以致电GC.Collect(),但据我了解,是否由 GC 决定是否收集内存。
            这反过来将为每个应该释放的对象调用Finalize() 方法。
            请注意,如果收集超出范围,GC 最终将收集图像使用的内存。
            如果您使用实现 IDisposeable 的集合,也可以使用 using 构造。这将保证在集合超出范围(或几乎在范围结束之后)时准确地释放对象。

            【讨论】:

            • 这个答案是完全错误的。 1. 致电GC.Collect() 是不好的做法,MS 不鼓励这样做。 2. GC.Collect 调用 Dispose,它调用 Finalize,但肯定不会在调用 GC.Collect 期间。 3. 虽然 GC 最终会清理内存,但在处理大块非托管内存时通常为时已晚,就像处理图像一样。这很容易导致内存不足异常。
            • @Steven 您评论的最后一部分是完全错误的;)。手动调用 GC 绝不会延迟任何垃圾收集。除了OOM和调用GC没有关系外,肯定不会增加OOM的风险。
            • @user492238:我并不是说调用GC.Collect 会增加发生OOM 的风险,但不调用对象上的Dispose() 会增加,而GC.Collect 不会带来这么多更好的。好的,你明白了.. 事实上它确实让事情变得“更好” ;-),因为此类对象的终结器通常会运行得更快,从而降低了 OOM 的机会。不过,请记住,GC 不会收集实现终结器并且没有立即释放的对象。它需要更高一代的收集或另一个 GC.Collect 调用才能真正清理这些对象。
            • @the_drow:您的更新更好,但仍然有点误导。你不应该建议 OP 打电话给GC.Collect,而只是让他打电话给Dispose 或使用using 关键字。
            • @Steven:为什么 GC.Collect() 不调用 Dispose() 方法?这没有任何意义。
            猜你喜欢
            • 2018-02-01
            • 1970-01-01
            • 1970-01-01
            • 2014-10-11
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2018-09-11
            • 1970-01-01
            相关资源
            最近更新 更多