如果我有一个实现IDisposable
的类的两个实例,如果第一个类中的非托管字段被分配给第二个类,那么它会发生什么。
例如,请考虑以下简化类:
public unsafe class Image : IDisposable
{
private float* pixelsBase;
private GCHandle pixelsHandle;
public Image(int width, int height)
{
this.Width = width;
this.Height = height;
// Assign the pointer and pixels.
this.Pixels = new float[width * height * 4];
this.pixelsHandle = GCHandle.Alloc(this.Pixels, GCHandleType.Pinned);
this.pixelsBase = (float*)this.pixelsHandle.AddrOfPinnedObject().ToPointer();
}
public float[] Pixels {get; private set;}
~ImageBase()
{
this.Dispose(false);
}
public void Dispose()
{
this.Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
// Dispose of any managed resources here.
}
if (this.pixelsHandle.IsAllocated)
{
this.pixelsHandle.Free();
this.pixelsBase = null;
}
}
}
pixelsBase
类实例中的Image
字段会被分配给另一个字段?
例如
var firstImage = new Image(100, 200);
var secondImage; new Image(300, 300);
firstImage = secondImage;
答案 0 :(得分:2)
IDisposable
与您的问题无关,因为您从未打过电话。唯一重要的是终结器 - 虽然IDisposable
和终结器是相关的,但运行时根本不关心IDisposable
。
那么,使用终结器会得到什么?当您的对象不再被引用时,其终结器将被放置在终结器队列中,除非发生进程终止,否则会在一段时间后执行。在您的情况下,这将释放GCHandle
并允许收集Pixels
字节数组。
不要尝试用C#编写C ++,或者用C ++理解C#。它们看起来很相似,但却非常不同 - 特别是在内存管理方面。 C#终结器与C ++析构函数几乎没有共同之处。
作为旁注,您希望避免长时间固定托管对象 - 它可以防止堆压缩正常工作,这意味着除非您的Pixels
数组位于LOH上,否则您将无法获得收集发生时回收的任何固定句柄下面的任何内存(免责声明:这是当前MS.NET运行时的实现细节;合同上,.NET甚至不需要 一个垃圾收集器,终结器不能保证永远运行,更不用说完成了。
如果你已经在处理指针,那么为你的数组分配非托管内存可能是个更好的主意。如果您确实希望为后备存储使用托管数组,则在需要非托管指针的范围内使用fixed
可能比在整个对象的生命周期内保留整个内容更好。实际上,您的解决方案需要至少两个集合来释放对象的内存 - 第一个最终调用GCHandle.Free
,第二个实际上回收现在不再固定的内存。即使您手动调用Dispose
,仍需要等待集合实际回收内存。