A large part of writing software is the management of resources. In a garbage collected environment a great deal of the management of memory is handled by infrastructure. This can lead to issues when developers fail to account for unmanaged elements and the restrictions of the garbage collector. Ultimately this is a question of responsibility.

When a software element creates a resource then it has a responsibility to handle cleanup of this resource. In the majority of cases it is appropriate to let the garbage collector handle this cleanup. The subset of resources that require explicit action to be cleaned up tend to be the set of resources it is most critical are properly dealt with. Failing to deal with these resources is a significant cause of scalability issues and stability problems due to resource exhaustion.

In identifying resources that need to be explicitly dealt with, there are a couple of obvious candidates:

  • Unmanaged resources are outside the scope of the garbage collector and need to be dealt with explicitly. Failure to do so will leak those resources when the reference goes out of scope and you're unlikely to get them back without killing the process.
  • Anything that implements IDisposable should have Dispose() called explicitly. This should be done as soon as is practical after the instance is no longer required.

Most other resources can be handled by the garbage collector but this is not universally true. In particular if you are dealing with a type that requires explicit cleanup but does not implement IDisposable you will need to ensure you perform this cleanup. This may be non-obvious, although the presence of methods with names such as Close or Shutdown is an indicator that this may be required.

The mechanism appropriate for cleanup of a resource for which you are responsible will vary depending upon whether it is a field on your type or a variable. If it is a field you will need to implement IDisposable using the standard pattern. If the resource is unmanaged you should ensure you have a finaliser that invokes the disposal code. This ensures that unmanaged resources will at worst be released when the garbage collector finalises the object rather than leaked. It is important that once an instance has been disposed that nothing uses it again. Failing fast by throwing ObjectDisposedException is generally preferable to using instances in a potentially inconsistent state.

Resources stored in variables should be cleaned up before the variable goes out of scope. If the variable is of a type that implements IDisposable then the using statement is generally the preferred way to accomplish this. This statement ensures that Dispose() is always called. As such it is effectively equivalent to a try-finally block. Where the resource does not implement IDisposable you will need to use try-finally to ensure that the resource is disposed. Cleanup code is placed in the finally block to ensure it is always called even if an exception is thrown. This is particularly important for unmanaged resources to ensure that they are not leaked. It is entirely acceptable to have catch blocks associated with the try in addition to the finally block.

In both cases the cleanup code should be sufficiently robust that the resources will be released regardless of what other state the instance may be in.

By default you should consider the responsibility for a resource to lie with the element that creates it. Responsibility may be passed to another element provided that the receiver accepts the responsibility. This requires that the contract of the receiving entity makes this transfer explicit, so that this behaviour may be relied upon and also so that the sender is aware of the transfer.

Software elements may also assume responsibilities through their use of resources. An element that maintains a reference to a resource is not necessarily responsible for the cleanup of that resource. It is however responsible for the reference and dealing with this reference has important implications. In a garbage collected system an instance will not be finalised and ultimately freed while references are maintained to it, even in this object has been disposed. If a component maintains a reference indefinitely to a resource that is used transiently then the resource has been leaked.

There are a number of common scenarios that result in this behaviour. These include:

  • Hooking up to events When an instance is subscribed to an event a reference to that instance is maintained until the publisher is garbage collected or the instance is explicitly unsubscribed. If the publisher is long lived or static failure to unsubscribe can keep the subscribed instance around for a significantly longer period than desired, up to potentially the life of the process. This is a resource leakage that can result in failure due to lack of resources. It may also result in incorrect behaviour due to stale instances responding to events outside the context in which they are expected to operate.
  • Static fields A static field will be in scope for the life of the AppDomain, which is generally the lifetime of the application. A static reference will keep an instance from being garbage collected effectively indefinitely. Instances that are intended to be transient must therefore be removed from static fields in a timely manner. I have particularly seen problems with static collections where instances are added but the code to remove them is omitted. This has the potential to over time be responsible for the leaking of numerous instances.
  • Failure to release references Long lived instances may need to maintain references to transient instances for some part of the lifetime of the transient instance. This is necessary and expected behaviour. It is important that the long lived instance have appropriate mechanisms to release the references so that the transient instances may be garbage collected. As with static fields collections have the potential to result in many instances being maintained for excessive periods.

Responsibility for these scenarios lies with the software element that creates them and must be handled or passed off to a software element that guarantees to handle them. The handling of responsibility is therefore the same as with resource creation but is not generally as explicit. Creating a resource implies a need for cleanup much more strongly than adding an instance to a collection or subscribing to an event. Software developers must therefore be mindful of the consequences of how they create and how they use resources.

In summary:

  • Creating and using resources implies responsibility
  • Failure to handle the responsibility can lead to a resource being leaked
  • As resources are finite leaking resources can result in resource exhaustion leading to stability and scalability issues
  • Managed environments are useful but cannot deal with all responsibilities
  • Taking responsibility is therefore a key concern for software developers in producing production quality code