[C#] I wish I knew : SafeHandle
I’ve been a professional C# developer for years now, but I’m still discovering trivial stuffs. In “I wish I knew”, I describe a feature that I missed.
Today, we’ll see how to use a SafeHandle
instead of implementing a finalizer.
Before starting, let’s define an imaginary unmanaged API:
static class SomeUnmanagedApi
{
[DllImport("SomeUnmanagedApi.dll")]
public static extern IntPtr CreateSomething();
[DllImport("SomeUnmanagedApi.dll")]
public static extern void DoSomething(IntPtr handle);
[DllImport("SomeUnmanagedApi.dll")]
public static extern void ReleaseSomething(IntPtr handle);
}
Clean up in a finalizer
Until recently, I assumed that the best way to clean up unmanaged resources was to implement a finalizer, like this:
class MyClass
{
readonly IntPtr handle;
public MyClass()
{
handle = SomeUnmanagedApi.CreateSomething();
}
~MyClass()
{
SomeUnmanagedApi.ReleaseSomething(handle);
}
}
Most of the time, I also want the class to be IDisposable
.
So I implement the complete Dipose pattern:
class MyClass : IDisposable
{
readonly IntPtr handle;
public MyClass()
{
handle = SomeUnmanagedApi.CreateSomething();
}
~MyClass()
{
Dispose(false);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
SomeUnmanagedApi.ReleaseSomething(handle);
if (disposing)
{
// dispose managed resources
}
}
}
But this is extremely cumbersome and error prone.
Clean up with a SafeHandle
Hopefully, I’ll never have to write that again :-)
The .NET Framework provides a class that manage all that for you: SafeHandle
.
It’s an abstract class that is able to handle virtually any kind of unmanaged resource.
To use it, you first need to create your own concrete SafeHandle
:
class SomeSafeHandle : SafeHandleZeroOrMinusOneIsInvalid
{
public SomeSafeHandle(IntPtr handle)
: base(true)
{
SetHandle(handle);
}
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
protected override bool ReleaseHandle()
{
SomeUnmanagedApi.ReleaseSomething(handle);
return true;
}
}
As, you see I didn’t derive directly from SafeHandle
, but instead I used SafeHandleZeroOrMinusOneIsInvalid
because I wanted values 0
and -1
to be treated as “invalid”.
The only thing I really had to implement is the ReleaseHandle()
methods. It will be called by SafeHandle
finalizer and Dispose()
.
Then, you can use the SafeHandle
as a managed resource:
class MyClass : IDisposable
{
readonly SomeSafeHandle handle;
public MyClass()
{
handle = new SomeSafeHandle(SomeUnmanagedApi.CreateSomething());
}
public void Dispose()
{
handle.Dispose();
}
}
See that ?
MyClass
doesn’t need a finalizer anymore, since it doesn’t own any unmanaged resource.
As a consequence, it’s not required to implement the full Dipose pattern.
Why is this a big deal ?
First of all, you get rid of the finalizer, which is great. I don’t think anyone likes to read code with finalizers.
Then, you don’t have to worry about unmanaged resources: you can treat a SafeHandle
as a managed resource.
Moreover, you are now sure that everything is implemented properly because Microsoft took care of all the details for you:
SafeHandle
callsReleaseHandle()
in its finalizerSafeHandle
implements the cumbersome Dispose patternSafeHandle
derives fromCriticalFinalizerObject
, so its finalizer is guaranteed to be called.
And lastly, the marshaller will implicitly wrap IntPtr
into SafeHandle
and vice versa.
This means that you can directly use your SafeHandle
class when doing P/Invoke call; the framework will do everything for your.
To illustrate this, I could rewrite my SomeUnmanagedApi
like this:
static class SomeUnmanagedApi
{
[DllImport("SomeUnmanagedApi.dll")]
public static extern SomeSafeHandle CreateSomething();
[DllImport("SomeUnmanagedApi.dll")]
public static extern void DoSomething(SomeSafeHandle handle);
// ReleaseSomething() is in SomeSafeHandle
}
Conclusion
I think I’ll never write a finalizer again, and I won’t miss it.
Also, before implementing your own SafeHandle
class, you should check if it already exists in the namespace Microsoft.Win32.SafeHandles
.
PS: The most up to date documentation about SafeHandle
is found in Reference Source