IDisisable,非托管字段,引用类型和分配

时间:2016-06-20 11:50:31

标签: c# unmanaged idisposable reference-type

如果我有一个实现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;

1 个答案:

答案 0 :(得分:2)

IDisposable与您的问题无关,因为您从未打过电话。唯一重要的是终结器 - 虽然IDisposable和终结器是相关的,但运行时根本不关心IDisposable

那么,使用终结器会得到什么?当您的对象不再被引用时,其终结器将被放置在终结器队列中,除非发生进程终止,否则会在一段时间后执行。在您的情况下,这将释放GCHandle并允许收集Pixels字节数组。

不要尝试用C#编写C ++,或者用C ++理解C#。它们看起来很相似,但却非常不同 - 特别是在内存管理方面。 C#终结器与C ++析构函数几乎没有共同之处。

作为旁注,您希望避免长时间固定托管对象 - 它可以防止堆压缩正常工作,这意味着除非您的Pixels数组位于LOH上,否则您将无法获得收集发生时回收的任何固定句柄下面的任何内存(免责声明:这是当前MS.NET运行时的实现细节;合同上,.NET甚至不需要 一个垃圾收集器,终结器不能保证永远运行,更不用说完成了。

如果你已经在处理指针,那么为你的数组分配非托管内存可能是个更好的主意。如果您确实希望为后备存储使用托管数组,则在需要非托管指针的范围内使用fixed可能比在整个对象的生命周期内保留整个内容更好。实际上,您的解决方案需要至少两个集合来释放对象的内存 - 第一个最终调用GCHandle.Free,第二个实际上回收现在不再固定的内存。即使您手动调用Dispose,仍需要等待集合实际回收内存。