繁体   English   中英

C#一次性物品

[英]C# disposable objects

是否有一些关于如何处理IDisposable对象序列的建议?

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

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

** 或者你可以建议我采取其他方法吗? **


通常,这似乎是同样的问题,例如,在没有共享指针的非托管C++ ,您可以拥有一个方法:

SomeObject* AllocateAndConstruct();

然后你不能确定何时处理它,如果你不使用代码合同或不在评论中陈述某些内容。

我猜一次性物体的情况大致相同,但我希望有一个合适的解决方案。

(来自这个问题)

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

当物体超出范围/范围时,GC不会立即发生; 这是不确定的。 当GC看到它时,没有必要做其他任何事情(终结者尚未处理),因为为时已晚。

那么,诀窍是知道你何时完成它,并自己调用Dispose() 在许多情况下using实现了这一点。 例如,您可以编写一个实现IDisposable的类并封装一组Image - 并使用包装您对该封装对象的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上显示数据,这有点棘手。 那里没有捷径; 您必须跟踪每个图像的完成时间,或接受非确定性的最终确定。

如果要确定处理集合中的对象,则应在每个对象上调用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#中,如果不处理对象,垃圾收集器最终将运行并清理它。

您应该以您不知道何时不再需要资源的方式设计系统。 在最糟糕的情况下,它们最终会在垃圾收集器到达时被处理掉,但IDisposable的意思是你可以提前释放重要的资源。

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

简洁的方法是创建自己的实现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中的所有bitMaps顺便说一下:如果你从文件中加载了位图而你忘了Dispose()你不知道何时可以删除该文件的位图。

您可以使用'using'块来确保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
    }
}

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

简洁的方法是创建自己的实现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;
            }
            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中的所有bitMaps顺便说一下:如果你从文件中加载了位图而你忘了Dispose()你不知道何时可以删除该文件的位图。

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM