I'm roughly familiar with the Dispose pattern for non-finalizable types, eg, types that wrap some sort of managed resource that we want to have deterministic cleanup done on. These sort of types typically do not implement a finalizer, since it is completely unnecessary.
However, I'm implementing a C# wrapper for a native API where I'm wrapping multiple, related unmanaged resources, and seemingly, would need multiple classes each implementing the finalizable-dispose pattern. The problem is that the guidelines for the dispose pattern says that finalizable A should not rely on finalizable B, which is exactly what I need:
Dispose Pattern on MSDN:
X DO NOT access any finalizable objects in the finalizer code path, because there is significant risk that they will have already been finalized.
For example, a finalizable object A that has a reference to another finalizable object B cannot reliably use B in A’s finalizer, or vice versa. Finalizers are called in a random order (short of a weak ordering guarantee for critical finalization).
So here are my constraints:
- To do anything, I have to create an "API" handle.
- To create "child" handles, I have to provide the API handle in the 'create child' call.
- Both types of handles can't be leaked.
- If the API handle is closed, all of its child handles are implicitly closed.
- To close a child handle, I have to provide the child handle and the API handle in the native call.
The native API looks something like this:
APIHANDLE GizmoCreateHandle();
CHILDHANDLE GizmoCreateChildHandle( APIHANDLE apiHandle );
GizmoCloseHandle( APIHANDLE apiHandle );
GizmoCloseChildHandle( APIHANDLE apiHandle, CHILDHANDLE childHandle);
The naive approach for this would be two parts:
- For the API handle, follow the typical SafeHandle pattern.
- For the child handle, follow the typical SafeHandle pattern, but modify it a little bit, since a child handle needs a reference to the API handle in order to implement its ReleaseHandle override - add a method that gives the API handle to the child SafeHandle after it's been constructed.
So everything would look something like this:
[DllImport( "gizmo.dll" )]
private static extern ApiSafeHandle GizmoCreateHandle();
[DllImport( "gizmo.dll" )]
private static extern void GizmoCloseHandle( IntPtr apiHandle );
[DllImport( "gizmo.dll" )]
private static extern ChildSafeHandle GizmoCreateChildHandle(ApiSafeHandle apiHandle);
[DllImport( "gizmo.dll" )]
private static extern void GizmoCloseChildHandle( ApiSafeHandle apiHandle, IntPtr childHandle );
[DllImport( "gizmo.dll" )]
private static extern void GizmoChildModify( ChildSafeHandle childHandle, int flag );
public class ApiSafeHandle : SafeHandle
{
public ApiSafeHandle() : base( IntPtr.Zero, true ) { }
public override bool IsInvalid
{
get { return this.handle == IntPtr.Zero; }
}
protected override bool ReleaseHandle()
{
GizmoCloseHandle( this.handle );
return true;
}
}
public class ChildSafeHandle : SafeHandle
{
private ApiSafeHandle apiHandle;
public ChildSafeHandle() : base( IntPtr.Zero, true ) { }
public override bool IsInvalid
{
get { return this.handle == IntPtr.Zero; }
}
public void SetParent( ApiSafeHandle handle )
{
this.apiHandle = handle;
}
// This method is part of the finalizer for SafeHandle.
// It access its own handle plus the API handle, which is also a SafeHandle
// According to MSDN, this violates the rules for finalizers.
protected override bool ReleaseHandle()
{
if ( this.apiHandle == null )
{
// We were used incorrectly - we were allocated, but never given
// the means to deallocate ourselves
return false;
}
else if ( this.apiHandle.IsClosed )
{
// Our parent was already closed, which means we were implicitly closed.
return true;
}
else
{
GizmoCloseChildHandle( apiHandle, this.handle );
return true;
}
}
}
public class GizmoApi
{
ApiSafeHandle apiHandle;
public GizmoApi()
{
this.apiHandle = GizmoCreateHandle();
}
public GizmoChild CreateChild()
{
ChildSafeHandle childHandle = GizmoCreateChildHandle( this.apiHandle );
childHandle.SetParent( this.apiHandle );
return new GizmoChild( childHandle );
}
}
public class GizmoChild
{
private ChildSafeHandle childHandle;
internal GizmoChild( ChildSafeHandle handle )
{
this.childHandle = handle;
}
public void SetFlags( int flags )
{
GizmoChildModify( this.childHandle, flags );
}
// etc.
}
However, now I have a flaw - my ChildSafeHandle's ReleaseHandle is referencing another handle to do its work. I now have two finalizable disposable objects, where one's finalizer depends on the other. MSDN explicitly says that finalizers should not rely on other finalizable objects, since they may already be finalized, or if .Net were to ever support multi-threaded finalization, they might be getting finalized concurrently.
What is the right way to do this? Do the rules allow me to access finalizable object A from the finalizer of B, so long as I test for validity first? MSDN isn't clear on this.