If I have two instances of a class that implement IDisposable
what happens to the unmanaged fields within the first class if it is assigned to the second.
For example, consider the following simplified class:
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;
}
}
}
What would happen to the pixelsBase
field in an instance of the Image
class was assigned to another one?
e.g
var firstImage = new Image(100, 200);
var secondImage; new Image(300, 300);
firstImage = secondImage;
IDisposable
has nothing to do with your question, since you're never calling it. The only thing that matters is the finalizer - while IDisposable
and finalizers are related, the runtime doesn't care about IDisposable
at all.
So, what do you get from using a finalizer? When your object is no longer referenced, its finalizer is put on the finalizer queue, and unless something process-killing happens, is executed after a while. In your case, this will release the GCHandle
and allow the Pixels
byte array to be collected as well.
Don't try to write C++ in C#, or understand C# in terms of C++. They look similar, but are very different - especially in memory management aspects. C# finalizers have very little in common with C++ destructors.
As a side-note, you want to avoid pinning managed objects for long - it prevents heap compaction from working properly, which means that unless your Pixels
array is on the LOH, you're not going to get any memory below any pinned handle reclaimed when a collection occurs (disclaimer: this is an implementation detail of the current MS.NET runtime; contractually, .NET doesn't even need a garbage collector at all, and finalizers aren't guaranteed to ever run, much less finish).
Allocating unmanaged memory for your array might be a better idea if you're already dealing with pointers anyway. If you really want to use a managed array for your backing store, using fixed
in a scope where you need the unmanaged pointer might be a better area than keeping the whole thing pinned for the lifetime of the whole object. In fact, your solution needs at least two collections to release the object's memory - the first one to eventually call the GCHandle.Free
, the second to actually reclaim the memory that is now no longer pinned. Even if you manually call Dispose
, you still need to wait for a collection to actually reclaim the memory.