Essential Interfaces: IDisposable

Support for class and enum definitions in version 5 brought PowerShell closer to being "a real .NET language" - but how can we make optimal use of this window into .NET's type system? Today we'll have a look at the IDisposable interface, what it's for, and how to use types that implement it in PowerShell!

What is IDisposable?

Since version 1.1, the .NET Framework ships with the System.IDisposable interface. Its vague description is as follows:

Provides a mechanism for releasing unmanaged resources.

So, what is an unmanaged resource?! To understand that, we first need to understand the difference between managed and unmanaged code.

Unmanaged vs Managed code

If you set out to write a program in C or C++ (unmanaged code), you have to explicitly manage your own memory allocations - if you want to store a 5KB image file in memory for analysis, you have to ask the OS for (at least) 5KB of memory using a memory allocation API (like malloc()), and more importantly, you need to tell the OS when the memory can be reclaimed (in C that would be free()). This direct access to raw memory makes C "powerful" in that the author of the code has complete control over how to optimize the use of the resources available, but it also makes it easy to introduce memory leaks and memory corruption errors, which in turn makes the language "unsafe" - there's nothing stopping you from violating the correctness of your running program.


In .NET on the other hand, all the memory allocations are taken care of for you by the runtime, and you don't need to worry about freeing memory that you no longer need - the runtime (more specifically, the garbage collector) will eventually get around to "clean up" the mess you've made. This is the selling point of .NET (and JVM FWIW) - providing a runtime with type systems as complex and (almost) as flexible as that of C++, but without the hairy shoot-yourself-in-the-foot memory allocation wizardry required of C.

44aysv

Unmanaged resources

But memory isn't the only type of resource we might want to acquire at runtime - writing to a file on disk will require us to ask the OS to query the file system, open the file with appropriate permissions and access flags, and then return a (potentially exclusive) file handle - and when we're done writing to the file, we might want to ensure that the file handle is returned to the OS as soon as possible, so that we don't block subsequent access to the file from another thread or process for longer than necessary. In this case, waiting for the garbage collector to eventually free our file resource acquisition is simply not enough.

So what we need is some common pattern that's safe to use for managing the lifetime of unmanaged resources that we acquire from the OS - something that can control the deallocation of file handles and the likes before having to wait for the GC to come around and deallocate the object that references these resources. This is where IDisposable comes in.


How to use IDisposable in PowerShell?

The IDisposable interface declares a single method - void Dispose(). The expectation is that the author of a class that acquires unmanaged resources puts the appropriate cleanup code in that method - and the user that creates an instance of said class in turn calls Dispose on the instance once the resources are no longer required.

Before we get into how one might implement this patter in a class, let's quickly review how to correctly use an instance of a type that implements IDisposable.

try{
    # Create IDisposable Instance
    $myDisposableInstance = [SomeDisposableType]::new()
    # Execute required work
    $myDisposableInstance.DoYourThing()
}
finally{
    if($myDisposableInstance -is [IDisposable]){
        $myDisposableInstance.Dispose()
    }
}

In the example above, [SomeDisposableType] is a type that implements IDisposable, and we wrap use of it in a try/finally statement - this ensures that if execution ever enters the try{} block, the code in the finally{} block is guaranteed to run as soon as we leave the try block, before we either execute any subsequent code or return to the caller.

Shouldn't [type]::new() go before the try block?

If you seek out advice on IDisposable usage patterns in a C# forum, you may have noticed countless examples that allocate the object with new type() immediately before enter the try block. So why would we put it inside the try block in PowerShell?!

The reason is that in C#, the assignment statement can be type-checked at compile-time, leaving instantiation of the object itself as the only thing that could possibly go wrong at runtime, which in turn makes the requirement to call Dispose() moot.

In PowerShell however, type constraints on assigment is only enforced at runtime, so you could run into a situation where a previously applied type constraint applies to $myDisposableInstance, and we risk running into [type]::new() actually succeeding in acquiring the underlying resource but PowerShell failing to complete the assignment, which is why we want to assure that we've already entered the try block at that point in time.

Why the -is [IDisposable] type check?

Given the above, $myDisposableInstance may be $null by the time we reach finally, and the -is [IDisposable] is simply a more specific way of checking for that - any of the following checks would've done as well:

if($null -ne $myDisposableInstance){ ... }
if($myDisposableInstance){ ... }
if($myDisposableInstance -is [object]){ ... }

I prefer the specific test for [IDisposable] as a favour to anyone who might need to read or refactor the code - I'm communicating "let's ensure that our disposable resource is actually just that".

What about using()?

You may have seen the using(...) statement in C# examples and thought "wait, how can we use IDisposable correctly if we don't have using()?" - but the truth is that using() is simply syntactical sugar - the C# compiler translates using() statements to try/finally statements internally:

// this piece of code
using(var instance = new DisposableType())
{
    // Work with instance
}

// produces the exact same IL as this
var instance = new DisposableType();
try{
    // Work with instance
}
finally {
    instance.Dispose();
}

When should I implement IDisposable?

There are two scenarios in which you'd want to implement the IDisposable interface in a PowerShell class:

  1. Your class acquires an unmanaged resource from the OS, usually via P/Invoke
  2. Your class creates an instance of another class that implements IDisposable

While the first scenario is probably going to be a bit of a rarity in PowerShell, the second scenario is much more likely to occur. In the following example I'm going to show how we might implement a class that uses a writeable FileStream object to create an empty .zip-file - while this example might seem contrived, you can easily imagine this being useful if you're modifying proprietary file formats!

The only thing we need to do in order to formally implement an interface is add the interface type name to the base class list of our class definition, and implement the required members (in this case the void Dispose() method):

class WriteableEmptyZipFile : IDisposable
{
    hidden [System.IO.FileStream]
    $_stream
    
    hidden [bool]
    $_headerWritten
    
    hidden [bool]
    $_directoryWritten

    WriteableEmptyZipFile([string]$path, [string]$name)
    {
        $file = New-Item -Path $path -Name $name -ItemType File
        $this._stream = $file.OpenWrite()
    }

    [void]
    WriteHeader()
    {
        if(-not $this._headerWritten){
            $this._stream.Write(@(80,75,3,6), 0, 4)
            $this._stream.Flush($true)
            $this._headerWritten = $true
        }
    }

    [void]
    WriteDirectoryStructure()
    {
        if(-not $this._headerWritten){
            $this._stream.Write(@(,0*18), 0, 18)
            $this._stream.Flush($true)
            $this._directoryWritten = $true
        }
    }

    [void]
    Dispose()
    {
        if($this._stream -is [IDisposable])
        {
            # optionally complete any work required to "close out" our file
            # $this.WriteHeader()
            # $this.WriteDirectoryStructure()

            # finally we call `Dispose()` on the objects under our control
            $this.Dispose()
        }
    }
}

And now, when a user (correctly) uses our IDisposable wrapper class, like so:

try{
  $newEmptyZip = [WriteableEmptyZipFile]::new()
  Invoke-SomethingThatMightThrow -ErrorAction Stop
  # more code before $newEmptyZip is written to
  # ...
}
finally{
  if($newEmptyZip -is [IDisposable]){
    $newEmptyZip.Dispose()
  }
}

... we can guarantee that the underlying FileStream object (and eventually it's underlying resource handle) is correctly released regardless of whether a terminating error is thrown inside the try block.

In other words, our IDisposable implementation helps the runtime resolve the whole chain (or hierarchy) of objects that depend on an unmanaged resource can signal the original acquirer that the resource can be free'd at the appropriate time:

WriteEmptyZipFile.Dispose() -> FileStream.Dispose() -> Cleans up actual file handle

Conclusion

  • IDisposable helps us manage the lifetime of unmanaged resource acquisitions in a deterministic fashion, even through deep object hierarchies
  • Using objects that implement IDisposable goes hand-in-hand with try/catch
  • IDisposable is straight-forward to implement - simply ensure you call Dispose() on any IDisposable object that the implementing class is responsible for creating
  • Always implement IDisposable:
    • When defining a class that stores a reference to an IDisposable that wasn't passed to it by the caller
    • When defining classes that acquire OS-managed resources via P/Invoke, for which clean up of any kind is required

That's it for now, keep an eye out for more on essential interfaces in PowerShell!