[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:

  1. SafeHandle calls ReleaseHandle() in its finalizer
  2. SafeHandle implements the cumbersome Dispose pattern
  3. SafeHandle derives from CriticalFinalizerObject, 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