Using, IDisposable & Finalizers

The Disposable Pattern, centered around the IDisposable interface, can be used to add deterministic disposal of resources that are not managed by the platform's memory allocation system (be it the garbage collector or reference counting). This typically includes non-memory resources such as file or network handles, database connections, or operating-system-level objects such as locks, window handles, or the like. On the Island-backed platforms and on Cocoa it could also include manually allocated memory, such as from calls to malloc().

The IDisposable interface is available on an platforms, and defines a single required method, Dispose. The implied contract is that when allocating any class that implements IDisposable, the Dispose method must be called (explicitly or implicitly, more on that later) when the class is done being used.

After the call to Dispose, the instance should be considered disposed, and no further calls to its members should be performed (unless otherwise documented as safe).

The implementation of Dispose will take care of releasing any resources held by the class that might need explicit disposal.

The Using Pattern & IDisposable

A common pattern for disposing short-lived (i.e. within the scope of a single method) objects is to the using (or __using, in Swift and try in Java) statement.

A using statement combines the declaration of a local variable that's limited in scope to the statement(s) contained within the using with an automatic call to Dispose at the end.

It serves two purposes: for one, because the local variable is limited in scope, accidental calls to it after disposal are prevented. For another, using automatically checks if the instance in question actually implements IDisposable, at runtime, and if so calls it in a way that is protected from exceptions:

using s := new FileSteam(...) do begin
  // work with the stream
end;
using (s = new FileSteam(...))
{
    // work with the stream
}
__using s = FileSteam(...) {
    // work with the stream
}
try (var s = new FileSteam(...)) {
    // work with the stream
}

The using statement roughly translates to the following manual code:

var s := new FileSteam(...);
try
  // work with the stream
finally
  (s as IDisposable):Dispose;
end;
var s = new FileSteam(...);
try
{
    // work with the stream
}
finally
{
    IDisposable(s)?.Dispose();
}
let s = FileSteam(...)
defer {
    (s as? IDisposable)?.Dispose()
}
// work with the stream
var s = new FileSteam(...);
try
{
    // work with the stream
}
finally
{
    if (s is IDisposable)
        IDisposable(s).Dispose();
}
s := FileSteam { ... }
defer func () {
    disposable, isDisposable := i.(sDisposable)
    if (isDisposable) { 
        disposable.Dispose(); 
    }
}()
// work with the stream

Finalizers

Finalizers are a secondary fall-back mechanism to free resources held by an object, if Dispose was not called properly (or if the class in question does not properly implement IDisposable). Finalizers are called when the last reference to an object is released (under ARC), or when the object is claimed by the garbage collector (under GC).

Note that under GC, the presence of finalizers adds extra cost to the deallocation and may cause instances to stay in memory longer and until a later GC cleanup than they would normally be cleaned away under. So it is always preferable to properly dispose of instances using the IDisposable pattern.

On the Cocoa platforms, including Cocoa object model classes on Island, Finalizers map to the dealloc method (or deinit, in Swift terms).

Suppressing Finalizers from Dispose

On .NET and Island, it is possible for the Dispose method to mark the current instance as disposed and forgo the overhead associated with a redundant call to the finalizer. This is achieved by calling the GC.SuppressFinalize (.NET) or Utilities.SuppressFinalize (Island) method.

Platfrom-specific Mappings

See Also