Object Destructors and Finalizers in .NET Using C# and Delphi for .NET

By: Brian Long

Abstract: This in-depth article looks at the role and usage of destructors (or equivalent) in the managed world of .NET. It examines in detail the areas of garbage collection, finalizers, the dispose pattern and language specifics, using both C# and Delphi for .NET

Brian Long (www.blong.com)

Table of Contents

Click here to download the files associated with this article.


Introduction

As we move into the .NET programming environment from traditional Windows programming models we need to adjust the way we think about writing classes. In particular we need to rethink our approach to destructors, as their role and their behaviour in the managed world of the Common Language Runtime (CLR) differs from what we are used to.

This article explains the new role for destructors, how they are considered from the CLR's point of view, what you should and shouldn't do in a destructor and how to follow the guidelines. A lot of the information here is targeted at component writers, but may well be useful to application programmers as well. You will see that the use of finalization code for objects (such as destructors) is a much rarer situation in .NET than it is in Win32 or Linux.

To exemplify how things are done in the unmanaged world of Win32 programming, both C++ and Delphi syntax will be employed (the Delphi language is used by Borland Delphi on Win32 platforms and Borland Kylix on Linux platforms). To demonstrate how things are done in the managed .NET arena, C# and Borland Delphi for .NET syntax will be used.

At the time of writing Delphi for .NET is currently a beta test release and so implementation details discussed in this article are subject to change in the commercial product release.

The Way It Was: Deterministic Destruction

When you write a class in Delphi or C++ you work on the basis that the programmer has to construct an instance of your class, and the responsible programmer will then explicitly destruct the instance when they are done with it. Destructing the instance involves invoking the objects destructor, which proceeds to free up any specific resources used by the object, such as blocks of memory, other objects and OS resources, and also frees the memory occupied by the objects instance data.

Note that a destructor in an unmanaged programming language serves two jobs:

  1. free resources used by the object
  2. free memory occupied by the objects instance data

If you have a hierarchy of classes, some or all of them may define specific destructors to tidy up resources that each individual class makes use of. When you destroy an object, its destructor frees its resources and then chains back to the ancestor classs destructor, which chains back to its ancestor class destructor and so on up to the base class. When all destructors have been called the instance data memory is freed.

Since any type of resource in Windows almost certainly involves the use of a handle to represent it (such as a window handle, a bitmap handle, a registry key handle, a file handle and so on), lets look at a simple class that wraps up the management of an arbitrary Windows handle. In truth we might inherit a variety of real classes from this one base class, but for simplicity well stick to just using the one class.

Destructors in C++

This is the class as implemented in C++:

#include <windows.h>
#include <stdio.h>

class BaseResource
{
private:
  // The resource handle
  HANDLE handle;
	
public:
  // Class constructor
  BaseResource(): hndl(INVALID_HANDLE_VALUE)
  {
    // Insert appropriate constructor code here to allocate the resource
    printf("BaseResource constructor should have code to allocate the resourcen");
  };

  // Class destructor
  ~BaseResource()
  {
    printf("BaseResource destructor frees the resourcen");
    if (handle != INVALID_HANDLE_VALUE)
      CloseHandle(handle);
  };

  void DoSomething(void)
  {
    printf("In BaseResource::DoSomethingn");
  }
};

C++ uses the new operator to construct a new object on the application heap. C++ heap-based objects are accessed by de-referencing a typed pointer and the delete operator is used to destruct them:

int main(int argc, char* argv[])
{
  // Construct resource object
  BaseResource *br = new BaseResource();
  try
  {
    // Use the resource object
    br->DoSomething();
  }
  __finally
  {
    // Destruct resource object
    delete br;
  }
  return 0;
}

The use of __finally in this C++ snippet is not ANSI C++ compliant, but both Microsofts and Borlands C++ compilers implement this keyword to make resource protection that much easier.


C:Temp>bcc32 ResourceObjectEg
Borland C++ 5.6 for Win32 Copyright (c) 1993, 2002 Borland
ResourceObjectEg.cpp:
Turbo Incremental Link 5.60 Copyright (c) 1997-2002 Borland

C:Temp>ResourceObjectEg
BaseResource constructor should have code to allocate the resource
In BaseResource::DoSomething
BaseResource destructor frees the resource

C:Temp>

C++ also supports constructing local objects on the stack, where neither pointers nor any special language operator are used. These objects are automatically destructed when they go out of scope:

int main(int argc, char* argv[])
{
  // Construct resource object
  BaseResource br;
  // Use the resource object
  br.DoSomething();
  return 0;
  // Resource object is automatically destructed when it goes out of scope
}

Destructors in Delphi

Delphi only supports heap-based objects, which are accessed through object references. Object references are pointers but do not use standard pointer syntax (for simplicity). Delphi objects are constructed by calling the constructor of the required class (typically called Create()) and are destructed by invoking the destructor of the class (called Destroy()). Delphi destructors are polymorphic and by convention they are not called directly; instead they are invoked indirectly through a call to the Free() method:

program ResourceObjectEg;

{$APPTYPE CONSOLE}

uses
  Windows;

type
  TBaseResource = class(TObject)
  private
    // The resource handle
    handle: THandle;
  public
    constructor Create;
    destructor Destroy; override;
    procedure DoSomething;
  end;

constructor TBaseResource.Create;
begin
  inherited Create;
  handle := INVALID_HANDLE_VALUE;
  WriteLn('TBaseResource constructor should have code to allocate the resource');
end;

destructor TBaseResource.Destroy;
begin
  WriteLn('TBaseResource destructor frees the resource');
  if handle <> INVALID_HANDLE_VALUE then
    CloseHandle(handle);
  inherited;
end;

procedure TBaseResource.DoSomething;
begin
  WriteLn('In TBaseResource.DoSomething')
end;

var
  BR: TBaseResource;
begin
  BR := TBaseResource.Create;
  try
    BR.DoSomething
  finally
    BR.Free
  end
end.

C:Temp>dcc32 ResourceObjectEg.dpr
Borland Delphi Version 15.0
Copyright (c) 1983,2002 Borland Software Corporation
ResourceObjectEg.dpr(49)
50 lines, 0.09 seconds, 12108 bytes code, 1817 bytes data.

C:Temp>ResourceObjectEg
TBaseResource constructor should have code to allocate the resource
In TBaseResource.DoSomething
TBaseResource destructor frees the resource

C:Temp>

Comments on Destructors

This process of explicitly destroying an object when its use has ended is referred to as deterministic destruction and is very common in object-oriented programming languages. Programmers using both Delphi and C++ programming languages consider it the norm. Unfortunately deterministic destruction is the cause of a worryingly large number of application bugs during (and after) application development, simply because the responsibility is with the programmer to destroy an object and to destroy it at an appropriate point.

Due to human error, it is common for objects to not be destroyed at all, yielding memory leaks. It is also common for an object to be destroyed and then later referred to by some code, potentially giving Access Violations or data corruption. Both these problems can be difficult to track down, unlike many normal logic problems.

Fortunately for us developers, these headaches are removed by .NET which dispenses with the requirements for deterministic destruction.

The Way It Is: Non-deterministic Finalization

The CLR uses garbage collection to avoid common application bugs such as those described above. The programmer no longer has the responsibility to destruct objects when they are finished with; in fact it is not possible for a programmer to destruct an object because the conventional notion of the destructor has gone.

.NET objects are all allocated on a managed heap. When objects are no longer referenced by any variables in an application (objects that are then unreachable by any code), they are clearly in need of disposal. However the programmer need do nothing to ensure that this will occur. Instead of this being the programmers responsibility, this is in the remit of the garbage collector.

The next time a garbage collection sweep of the managed heap takes place all unreachable objects will be identified, their memory reclaimed and the managed heap compacted. When the garbage collector will actually do this is tricky to predict in real time, however the algorithm it uses is well documented. You can also force a garbage collection sweep if need be, though such requirements are quite rare.

The point established here is that without any programmer intervention the instance data space occupied by any .NET object will be automatically freed after it has been finished with. This means that one part of the job of the destructor has been completely dispensed with.

However there is also the other key job of a destructor to take into consideration. A destructor should dispose of any additional resources used by the object:

  • If the object has references to other objects, the traditional destructor would need to destruct these as well. This is no longer necessary in .NET as the garbage collector will reclaim their instance memory at some point.
  • Any unmanaged resources, such as raw database connections and file handles, and so on, will still need to be cleaned up and the garbage collectors helpful data space reclamation does not cater for this side of things, in itself.

To be clear, it is not that common for .NET classes to directly make use of unmanaged resources. It is more common for them to use other managed objects in the .NET FCL (Framework Class Library) that already take the responsibility for dealing with unmanaged resources (unmanaged resource wrapper objects). For example, files are usually represented by instances of the System.IO.FileStream class, and database connections might well be represented by instances of System.Data.SqlClient.SqlConnection or System.Data.OleDb.OleDbConnection.

It is typically component writers who have to worry about accessing and using unmanaged resources directly, as most standard cases are covered by existing FCL classes. Once in a while, however, you might be in a situation where you need to know how to correctly deal with disposing of an unmanaged resource. Fortunately, all .NET objects offer an opportunity to ensure that any unmanaged resources are properly disposed of through their Finalize() method.

Application programmers do have their own issues that need to be looked at, since you will often be working with managed objects that control unmanaged resources (such as the file and connection classes) and you may wish to free up these resources ahead of the point that the garbage collector will wish to do this. We'll address this issue shortly.

Finalizers

The base class in the .NET environment, System.Object, defines a protected virtual method call Finalize(). The System.Object implementation of Finalize() is a placeholder and does nothing, but this method can be overridden by any class that requires an opportunity to do unmanaged resource cleanup. A class that overrides Finalize() is sometimes described as having a finalizer, or being a finalizable object.

When the garbage collector determines an object is unreachable, it will check to see if it has a finalizer and if so, will call the finalizer before reclaiming the objects memory (the actual implementation of this is discussed later). This now allows a managed .NET object to offer the same clean-up behaviour as attained with an unmanaged objects destructor. The garbage collector invokes the finalizer to free specific unmanaged resources and then proceeds to reclaim the instance data memory.

Remember that the requirement for a finalizer is quite rare and only applies when you have unmanaged resources that need to be freed.

Some Finalizer Issues

The primary problem we face with a finalizer is that we still have no control over when the garbage collector will come along and tidy up our object (call the finalizer, if present, and also reclaim the object's memory). If the resource held by the object should be freed promptly (such as an unmanaged database connection), simply overriding the Finalize() method wont help us out.

This whole issue is described by the term non-deterministic finalization. Finalization of the object will occur, but not when the programmer wants it to; instead it occurs when the garbage collector gets around to it.

Since Finalize() is protected, it cannot be called directly by a consumer of the object to overcome this problem. However you could implement a public method that calls Finalize() and use that to permit consumers to free up the objects resources at a specific point in time. If you were to do this you would then need to tell the garbage collector that it does not need to call the finalizer when collecting the object.

This approach is not encouraged though for a couple of reasons. Firstly, there is a formal mechanism designed to help support deterministic finalization, which we will look at later: the dispose pattern. Secondly, in C# there is no way to explicitly call the finalizer from any other method, as we will see in the next section. Of course we could overcome this issue by implementing a public method that freed the unmanaged resources, and was called from the finalizer with appropriate checks, but again, the formal approach is preferred.

Another problem is that you cannot guarantee the order in which object finalizers will execute, even when one object holds a reference to another object and they both have finalizers. This means that finalizers should never access other finalizable objects (objects without finalizers, however, are just fine); they should generally be restricted to simply freeing the unmanaged resource.

Finalizers in C#

Whilst System.Object defines the Finalize() method, it is not possible to directly override it in a C# program. Instead, the designers of the language chose to enforce a specific piece of custom syntax for the job. In order to write a finalizer method in C# you use the same syntax as a C++ destructor does.

In fact the C# language uses the term destructor for its finalizers, which has been a source of confusion for developers migrating to C# from C++. It was arguably a poor choice for the language designers to call a C# finalizer a destructor, particularly when C++ destructors are encouraged but C# destructors are discouraged.

So in C#, what looks like a destructor and is called a destructor is in truth a finalizer, as far as the CLR is concerned. When C# compiles a destructor, the CIL code emitted will turn it into an override of the protected virtual Finalize() method.

This means that, unlike a C++ destructor, which is explicitly invoked by the programmer through the use of the delete operator (or implicitly invoked when a stack-based object goes out of scope), a C# destructor is invoked implicitly by the garbage collector at some undetermined point after the object has become unreachable. Additionally, unlike a C++ destructor a C# destructor does not free up the instance data memory (this occurs at some point after the destructor executes, as will be explained later).

The unmanaged resource wrapper class from earlier deals with a Win32 handle. This is an example of an unmanaged resource that will require some cleanup (you must close the handle) and so warrants finalizer (or destructor) to do this. The class could be written and used in C# like this:

using System;
using System.Runtime.InteropServices;

class BaseResource
{
    // The resource handle
    private IntPtr handle = IntPtr.Zero;

    // Class constructor
    public BaseResource()
    {
        Console.WriteLine("BaseResource constructor should have code to allocate the resource");
    }

    [DllImport("kernel32.dll")]
    static extern bool CloseHandle(IntPtr hObject);
    
    // Class destructor
    ~BaseResource()
    {
        Console.WriteLine("BaseResource destructor (finalizer) frees the resource");
        if (handle != IntPtr.Zero)
          CloseHandle(handle);
    }

    public void DoSomething()
    {
        Console.WriteLine("In BaseResource.DoSomething");
    }
}

class MainApp
{
    [STAThread]
    static void Main()
    {
        BaseResource br = new BaseResource();
        br.DoSomething();
    }
}


C:Temp>csc ResourceObjectEg.cs
Microsoft (R) Visual C# .NET Compiler version 7.00.9466
for Microsoft (R) .NET Framework version 1.0.3705
Copyright (C) Microsoft Corporation 2001. All rights reserved.


C:Temp>ResourceObjectEg
BaseResource constructor should have code to allocate the resource
In BaseResource.DoSomething
BaseResource destructor (finalizer) frees the resource

A quick look in the Microsoft .NET IL Disassembler (ildasm.exe) shows that the BaseResource class does indeed have a Finalize() method:

You can think of the C# destructor above as being compiled by the C# compiler as if it were written like this:

protected override void Finalize()
{
    try
    {
        Console.WriteLine("BaseResource destructor (finalizer) frees the resource");
        if (handle != IntPtr.Zero)
          CloseHandle(handle);
    }
    finally
    {
        base.Finalize();
    }
}

Again, using IL DASM (ildasm.exe), we can verify this by disassembling the Finalize() method. Notice the try{}finally{} statement that ensures the call back up to System.Object.Finalize() occurs regardless of whether an exception occurs or not:

You should be aware that there are overheads with finalizers and Microsoft strongly advises against implementing destructors in C#. Their coding guideline is: If you can do the same thing without a C# destructor, do it.

Finalizers in Delphi for .NET

In the case of Delphi for .NET you are free to override Finalize() in your classes and, if you felt the need, to declare a public method to expose the Finalize() method to your object consumers (again, you should typically use the dispose pattern rather than do this).

This is how the unmanaged resource wrapper class from earlier could be written and used in Delphi for .NET, using a finalizer:

program ResourceObjectEg;

{$APPTYPE CONSOLE}

uses
  Borland.Win32.Windows;

type
  BaseResource = class
  private
    // The resource handle
    handle: IntPtr;
  protected
    procedure Finalize; override;
  public
    constructor Create;
    procedure DoSomething;
  end;

constructor BaseResource.Create;
begin
  inherited Create;
  handle := IntPtr.Zero;
  WriteLn('BaseResource constructor should have code to allocate the resource');
end;

procedure BaseResource.Finalize;
begin
  try
    WriteLn('BaseResource finalizer frees the resource');
    if handle <> IntPtr.Zero then
      CloseHandle(handle.ToInt32);
  finally
    inherited
  end
end;

procedure BaseResource.DoSomething;
begin
  WriteLn('In BaseResource.DoSomething')
end;

var
  BR: BaseResource;
begin
  BR := BaseResource.Create;
  BR.DoSomething
end.


C:Temp>dccil ResourceObjectEg.dpr
Borland Delphi Version 16.0
Copyright (c) 1983,2002 Borland Software Corporation
Confidential pre-release version built Nov 14 2002 17:05:31
ResourceObjectEg.dpr(6) Warning: Unit 'Borland.Win32.Windows' is experimental
ResourceObjectEg.dpr(49)
50 lines, 0.17 seconds, 6388 bytes code, 0 bytes data.

C:Temp>ResourceObjectEg
BaseResource constructor should have code to allocate the resource
In BaseResource.DoSomething
BaseResource finalizer frees the resource

C:Temp>

Notice that there is no call to BR.Free as in the unmanaged version, however you could put one in and it would have no effect at all on the code. Well come back to look at the Delphi for .NET Free() method later.

Garbage Collector Details

In truth, the operation of the garbage collector is more involved than how it has been described so far. This section explores the garbage collectors modus operandi in more depth to clarify certain issues that arise.

Generational Algorithm

To encourage runtime efficiency the garbage collector uses generations. Generations are logical divisions of the managed heap and the CLR uses three generations: generation 0, generation 1 and generation 2. New objects are always allocated from the generation 0 portion of the heap.

When an object is allocated and generation 0 is too full to accommodate it, the garbage collector will start a generation 0 sweep looking for unreachable objects in generation 0 and reclaiming their memory. Any reachable objects are then promoted to the generation 1 heap area, thereby leaving generation 0 empty.

Any objects that get moved around in memory through this generational promotion have all their references in the application updated to reflect their new address. This means you cannot assume an object will remain where it started out throughout its life; it may get moved by the garbage collector. However if necessary you can pin an object in place so it will not be moved (using the C# fixed statement or the System.Runtime.InteropServices.GCHandle structure's Alloc() and Free() methods).

During a garbage collection, whilst generation 0 objects are being promoted to generation 1 it may be the case that generation 1 is too full to accommodate some or all of them. In this case the garbage collector will scan generation 1, reclaiming memory for unreachable objects and promoting reachable objects to generation 2.

At some point, whilst promoting objects from generation 1 to generation 2, it may be the case that generation 2 fills up. If so, the garbage collector will scan generation 2 and reclaim memory for unreachable objects to regain some space. Reachable objects in generation 2 remain in generation 2.

So generation 0 contains new objects that have not been examined by the garbage collector, generation 1 contains objects that have been examined once by the garbage collector (and were still reachable at that point) and generation 2 contains objects that been examined at least twice (and were still reachable).

The idea behind this type of garbage collection algorithm involves a number of assumptions:

  • Newer objects will have short lifetimes. They are allocated in generation 0 and generation 0 is what the garbage collector examines by default.
  • Older objects will have longer lifetimes. They get promoted to generation 1 or generation 2, which are areas of the managed heap that are garbage collected less frequently.
  • It is more efficient to garbage collect a portion of the heap, rather than the whole managed heap, which is why the garbage collector only scans generation 0, by default. Microsoft's own performance tests indicate that it takes between 0 and 10 milliseconds to garbage collect generation 0. Collection of Generation 1 is typically between 10 and 30 milliseconds.

It was mentioned earlier that you can force the garbage collector to sweep the managed heap, if you feel it is necessary. This is achieved by calling System.GC.Collect(), but you should be aware that it is not recommended to do this. Firstly, the main reason people invoke the garbage collector is to reduce periodical sluggishness in the application when it occurs naturally. They will invoke the garbage collector during UI operations (or other naturally lengthy processes in the application) so the overhead is not noticeable. However the timing information given above demonstrates that garbage collection is typically not a time consuming operation.

Most information about the managed heap generation indicates their sizes as follows:

  • Generation 0 starts off with a threshold around 256 kB
  • Generation 1 starts off with a threshold around 2 MB
  • Generation 2 starts off with a threshold around 10 MB

However information from Microsoft CLR engineers suggests that the generation 0 and generation 1 thresholds start out at different levels. The generation 0 threshold defaults to the size of the L2 on-chip cache (also called the Level 2 cache, or secondary cache). The initial minimum threshold for generation 1 is about 300kB, whereas the maximum size can be half the segment size, which for the regular single processor workstation GC will amount to 8MB. The plan being that most generation 0 allocations (i.e. managed objects) will live and die entirely on the CPU chip in the very fast L2 cache.

The garbage collector, when operating off its own bat, keeps an eye on how the application is allocating memory (through object construction). If necessary it will modify the thresholds of each of the managed heap generations. The second reason for not manually invoking the garbage collector is that this will break its statistical analysis of the program, which it uses to make decisions on any fine-tuning of these thresholds.

More Finalizer Issues

We already bumped into a couple of issues with finalizers earlier, however there are more details we should know about with regard to the execution of finalizers in order to fully appreciate the situation.

When an object with a finalizer is first created the CLR adds a reference to it onto an internal list called the finalization list. This makes it easy for the garbage collector to know which objects require finalization before having their memory reclaimed. Note that this in itself adds a little overhead onto the construction of any objects that have finalizers.

When the garbage collector does a sweep of the managed heap and finds objects that are unreachable by program code (which are essentially garbage to be collected), it then checks to see if any of them appear in the finalization list. Any that do need their finalizers called and so cannot have their memory reclaimed just yet. These objects have a reference to them added to another internal list, called the freachable queue (pronounced F Reachable) and their reference in the finalization list is removed. This tells us that finalizable objects slow down the garbage collector, since each garbage collection sweep has to be accompanied by searches through the finalization list (a check is made for each unreachable object to see if it is in the finalization list).

The idea of the freachable queue is that since the objects in it need to have a method executed on them (the finalizer), they must still be considered reachable. Because of this these objects are promoted up to the next generation of the heap, thereby morphing from garbage into non-garbage (albeit temporarily), and the garbage collector then reclaims any memory from objects that were unreachable and had no finalizers.

So what happens next to these objects that survived the garbage collector? Well, there is a dedicated high priority thread (the finalizer thread) managed by the CLR that keeps an eye on the freachable queue. When the queue is empty the finalizer thread sleeps, but when any objects are added to the queue it gets woken up and starts sequentially calling their finalizers.

From this we learn that finalizers are not called on our normal applications thread. The implication of this is that it is important to ensure your finalizer operates as quickly as possible and doesnt do any blocking (waiting for another thread or some other resource to become available), as discussed shortly.

As soon as an objects finalizer has been called, the object is removed from the freachable queue and is now truly garbage waiting to be collected. However this will not happen until the next time the garbage collector sweeps the heap generation occupied by the object, which will generally not be generation 0 (since it was promoted when moved from the finalization list to the freachable queue), and so will almost certainly occur later than the very next garbage collection sweep, which will just examine generation 0.

This tells us that objects with finalizers live much longer than those without and also that they take at least two garbage collection cycles to have their memory reclaimed. An important consequence of this is that any other objects referenced by the finalizable object (and any objects that those objects refer to, and so on) will also be kept around much longer than you might otherwise expect, since they will remain reachable until the finalizable object is finalized.

In fact a finalizable object can also be resurrected during the execution of its finalizer to extend its lifetime even further. A call to System.GC.ReRegisterForFinalize() adds the object passed as a parameter to the finalization list (there are few circumstances that this is beneficial), ensuring that when it is next considered unreachable its finalizer will be called again.

Whilst the finalizers are called sequentially for the objects in the freachable queue you cannot predict in what order the objects are placed in the queue (thats dependant on the order in which the garbage collector discovers they are unreachable, which cannot be determined). This means that you cannot predict which order the finalizers are called. Even when one object with a finalizer holds a reference to another object with a finalizer, the two finalizers could be called in either order.

This means that a finalizer must not refer to any other objects that have finalizers, using an assumption that a finalizer has or has not been called. In general finalizers should simply free the objects resources and do nothing else.

If an unhandled exception occurs in a finalizer the CLRs executing thread will swallow the exception, treat the finalizer as if it completed normally, remove it from the freachable queue and move onto the next entry.

More serious though, is what happens if your finalizer doesn't exit for some reason, for example it blocks, waiting for a condition that never occurs. In this case the finalizer thread will be hung, so no more finalizable objects will be garbage collected. You should be very much aware of this situation and stick to writing the simplest code to free your unmanaged resources in finalizers.

Another consideration is what happens during application shutdown. When the program shuts, the garbage collector will endeavour to call the finalizers of all finalizable objects, but with certain limitations:

  • Finalizable objects are not promoted to higher heap generations during shutdown.
  • Any individual finalizer will have a maximum of 2 seconds to execute; if it takes longer it will be killed off.
  • There is a maximum of 40 seconds for all finalizers to be executed; if any finalizers are still executing, or pending at this point the whole process is abruptly killed off.

The invocation of finalizers is dependant on the garbage collector and so is non-deterministic, which may well be inappropriate for some types of resource, for example unmanaged database connections. The next section looks at a way of allowing deterministic finalization. However it is possible to manually invoke the garbage collector, if appropriate (and there are not many cases where it is), then wait for all objects that are on the freachable queue to have their finalizers called, then re-run the garbage collector again (to collect all the objects that have just been finalized). This is to reclaim as much space as possible before continuing execution and can be achieved like this:

System.GC.Collect();
System.GC.WaitForPendingFinalizers();
System.GC.Collect();

Calling the garbage collectors Collect() method without any parameters forces it to run across all three generations and reclaim space for all unreachable objects on the managed heap. There is an overloaded version of Collect() that takes an integer parameter to limit which generations to collect from.

You can find more information on how the garbage collector works in Jeffrey Richters two-part article on the subject from MSDN Magazine in November and December 2000 (see the Further Reading section).

The Dispose Pattern: Deterministic Finalization

There are a couple of scenarios that may require the option for deterministic finalization of an object (forciing it to be finalized at a programmer-determined point):

  1. You have written a class that makes use of managed objects that represent unmanaged resources (for example file or database connection objects from the FCL), and it is appropriate that an object consumer can cause those wrapped managed resources to be released at a fixed point. In other words you wish to somehow expose the finalizers of the objects you are using to the object consumer. This could be described as a class using unmanaged resource wrapper objects
  2. You have written a class that makes use of an unmanaged resource, and it is appropriate that an object consumer can cause the unmanaged resource to be released at a fixed point. In other words you wish to somehow expose your finalizer to the object consumer. This could be described as a class using unmanaged resources..

The first scenario is much more frequent than the second one, although sometimes a given class will fit into both scenarios.

In both cases it is quite feasible for the programmer to concoct some scheme to allow deterministic finalization of an object as we saw earlier. However you would be advised instead to follow the mechanism provided for this purpose, which is to implement the dispose pattern. This pattern formally defines how to offer deterministic finalization to an object consumer, giving consistency to developers using your objects.

To implement the dispose pattern, your object must implement the System.IDisposable interface. This is a simple interface with only one member, a parameterless method called Dispose() that does not return a value (i.e. a void function, or a procedure). When a class implements IDisposable it makes the Dispose() method publicly available as a means to free up the objects unmanaged resources, be they directly or indirectly used by the object (however the objects memory will still be reclaimed later, by the garbage collector).

When examining classes that implement the dispose pattern you may well find they offer an alternative method to do the same job, called Close(). This is merely a convenience to the programmer using the objects, as it often seems more appropriate to close some types of resource (such as files or database connections) than to dispose of them. Note that Close() is not part of the dispose pattern, it is simply an optional alternate entry point to get to the Dispose() method. Typically Close() will simply call Dispose(), giving exactly the same result.

Implementing IDisposable.Dispose()

When implementing the dispose pattern, the Close() method (if present) should be public and non-virtual and simply call Dispose().

The Dispose() method from the IDisposable interface should free any unmanaged resources owned by the object (either directly, or indirectly through other objects) and should be implemented so it can be called multiple times without throwing any exceptions. Additionally, Dispose()should be public and also sealed (in C# terms) or final (in CIL and Delphi for .NET terms) so it cannot be overridden in descendant classes.

The behaviour of both the C# and Delphi compilers when implementing an interface method is to ensure it is virtual (this is a CLR requirement). If the method is actually declared with the virtual modifier, then things stay like that. However a method that was not declared virtual will be compiled as if it were defined with the virtual and also the sealed (C#) or final (Delphi) modifiers. This is done automatically to avoid problems with polymorphism in descendants, since the ancestor has a virtual method that isn't supposed to be virtual according to the source code.

In brief, the following two points describe what you do to implement the dispose pattern for the two different types of classes you might need to write. The following sections will go into the details and show example code to help make this clear:

  • A class that matches scenario 1 implements Dispose() and from there calls the Dispose()/Close() methods of the unmanaged resource wrapper objects.
  • A class that matches scenario 2 implements an internal Dispose() method that actually does the cleanup. It also implements both IDisposable.Dispose() and a finalizer, both of which call the internal Dispose() routine, but IDisposable.Dispose() also tells the garbage collector not to call the finalizer.

The Dispose Pattern and Unmanaged Resource Wrappers

If you are writing a class that makes use of objects that use unmanaged resources (as in scenario 1 above), then it is a simple matter to implement the dispose pattern. You implement Dispose() to call the Dispose() or Close() methods of all your unmanaged resource wrapper objects.

Let's use an example of a simple class that uses a FileStream object to access a file. FileStream is a class that uses an unmanaged resource (a file handle) and implements the dispose pattern. However, unlike most classes it keeps the Dispose() method protected and only offers you the public Close() method to release the resource.

This is how it is implemented in C#:

using System;
using System.IO;

class BaseResource: IDisposable
{
    // Track whether Dispose has been called.
    private bool disposed = false;

    // The unmanaged resource wrapper object
    private FileStream file;

    // Class constructor
    public BaseResource(String fileName)
    {
        Console.WriteLine("BaseResource constructor allocates unmanaged resource wrapper");
        file = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read);
    }

    // Implement IDisposable
    public void Dispose()
    {
        // Check to see if Dispose has already been called.
        if (!disposed)
        {
            Console.WriteLine("Dispose pattern releases unmanaged resource wrapper's resource");
            file.Close();
            disposed = true;
        }
    }

    public void Close()
    {
        Dispose();
    }

    public void DoSomething()
    {
        Console.WriteLine("In BaseResource.DoSomething");
    }
}

Now that we have implemented the dispose pattern, the programmer who uses the class has the option of letting the usual non-deterministic finalization deal with closing the file, since the FileStream class will close the file in its finalizer (C# destructor). However they can also explicitly close the file by calling Dispose() or Close() if needed. If you create an instance of BaseResource class, use it and then call one of these two methods you get the following output. Note that we will look specifically at how to use disposable objects in a later section.


C:Temp>csc ResourceObjectEg.cs
Microsoft (R) Visual C# .NET Compiler version 7.00.9466
for Microsoft (R) .NET Framework version 1.0.3705
Copyright (C) Microsoft Corporation 2001. All rights reserved.


C:Temp>ResourceObjectEg
BaseResource constructor allocates unmanaged resource wrapper
In BaseResource.DoSomething
Dispose pattern releases unmanaged resource wrapper's resource

C:Temp>

This is how the dispose pattern looks in Delphi for .NET:

uses
  System.IO;

type
  BaseResource = class(System.Object, IDisposable)
  private
    // Track whether Dispose has been called.
    disposed: Boolean;
    // The unmanaged resource wrapper object
    fileStream: FileStream;
  public
    constructor Create(const fileName: String);
    // Implement IDisposable and ensure IDisposable.Dispose is not overridable
    procedure Dispose;
    procedure Close;
    procedure DoSomething;
  end;

constructor BaseResource.Create(const fileName: String);
begin
  inherited Create;
  WriteLn('BaseResource constructor allocates unmanaged resource wrapper');
  fileStream := System.IO.FileStream.Create(
    fileName, FileMode.Open, FileAccess.Read, FileShare.Read);
end;

procedure BaseResource.Dispose;
begin
  // Check to see if Dispose has already been called.
  if not disposed then
  begin
    WriteLn('Dispose pattern releases unmanaged resource wrapper''s resource');
    fileStream.Close;
    disposed := True
  end
end;

procedure BaseResource.Close;
begin
  Dispose
end;

procedure BaseResource.DoSomething;
begin
  WriteLn('In BaseResource.DoSomething')
end;

With code that creates and uses the object and then calls its Dispose() or Close() methods you get this output:


C:Temp>dccil ResourceObjectEg.dpr
Borland Delphi Version 16.0
Copyright (c) 1983,2002 Borland Software Corporation
Confidential pre-release version built Nov 14 2002 17:05:31
ResourceObjectEg.dpr(59)
60 lines, 0.28 seconds, 5744 bytes code, 0 bytes data.

C:Temp>ResourceObjectEg
BaseResource constructor allocates unmanaged resource wrapper
In BaseResource.DoSomething
Dispose pattern releases unmanaged resource wrapper's resource

C:Temp>

In both these classes the Dispose() method uses a private data field, disposed, to decide if it's already been called. This way, calling Dispose() or Close() multiple times is completely harmless. However, this implementation of the dispose pattern is not thread-safe. Another thread could start disposing the object after the unmanaged resource wrappers are disposed, but before the internal disposed field is set to true. If you were writing a class for use in a multi-threaded application and were ensuring the class was thread-safe then this is how you would do it for the Dispose() method. The following modification to Dispose() remedies this by locking the object for the method's duration to prevent any other threads calling Dispose() at the same time.

Here is the C# rewrite:

// Implement IDisposable
public void Dispose()
{
    lock(this)
    {
        // Check to see if Dispose has already been called.
        if (!disposed)
        {
            Console.WriteLine("Dispose pattern releases unmanaged resource wrapper's resource");
            file.Close();
            disposed = true;
        }
    }
}

This is what it looks like in Delphi for .NET. Notice that Delphi does not have a lock keyword so the blocking code has to be written by hand with System.Threading.Monitor static class methods and a try/finally statement.

uses
  System.IO,
  System.Threading;
...
procedure BaseResource.Dispose;
begin
  // Make this routine thread-safe
  Monitor.Enter(Self);
  try
    // Check to see if Dispose has already been called.
    if not disposed then
    begin
      WriteLn('Dispose pattern releases unmanaged resource wrapper''s resource');
      fileStream.Close;
      disposed := True
    end
  finally
    Monitor.Exit(Self)
  end
end;

Well look at the typical Delphi for .NET syntax for using such an object shortly.

Delphi for .NET Destructors and IDisposable

If you are using Delphi for .NET, you can clearly implement IDisposable as we have just seen. However, because most developers using Delphi for .NET are likely to have been using Delphi for Win32 beforehand, the Borland R&D engineers decided to try and make things a little easier for you if you are using resource wrapper objects in your class. The compiler will silently implement IDisposable for you if it sees that you have defined a destructor exactly matching this signature (the typical Delphi destructor signature):

destructor Destroy; override;

If you have such a destructor, the class will be internally marked as implementing the IDisposable interface (it is an error to try and explicitly declare that IDisposable is implemented in the class). As usual, IL DASM confirms this is the case:

Also, a destructor with this signature will be marked as an implementation of IDisposable.Dispose() allowing you to do your cleanup in a familiar location. Here IL DASM shows the code behind an empty Delphi destructor and you can see that whilst the method is called Destroy(), it is an override of IDisposable.Dispose():

This shortcut is quite convenient for objects that make use of unmanaged resource wrapper objects. Your destructor calls the Dispose() methods of the wrapper objects and your object consumer either invokes the destructor (causing deterministic finalization) or doesn't (allowing non-deterministic finalization). Additionally, the presence of the destructor will be useful while porting code to .NET, and also when trying to write cross-platform code that works on Win32 (compiled with Delphi), Linux (compiled with Kylix) and .NET (compiled with Delphi for .NET).

To enhance the feeling of familiarity the implementation of the Free() method will check to see if the IDisposable interface is implemented, and if so, it calls the interfaces Dispose() method for you. If you follow the destructor signature above then Free()will invoke the destructor, but if you implement Dispose() as we did the previous section then your Dispose() method will be called.

What all this means is that you can implement the class along the following lines. Note that the class does not claim to implement IDisposable; that will happen behind the scenes and would produce a compiler error if it did.

uses
  System.Threading,
  System.IO;

type
  BaseResource = class
  private
    // Track whether Dispose has been called.
    disposed: Boolean;
    // The unmanaged resource wrapper object
    fileStream: FileStream;
  public
    constructor Create(const fileName: String);
    destructor Destroy; override;
    procedure Close;
    procedure DoSomething;
  end;

constructor BaseResource.Create(const fileName: String);
begin
  inherited Create;
  WriteLn('BaseResource constructor allocates unmanaged resource wrapper');
  fileStream := System.IO.FileStream.Create(
    fileName, FileMode.Open, FileAccess.Read, FileShare.Read);
end;

destructor BaseResource.Destroy;
begin
  // Make this routine thread-safe
  Monitor.Enter(Self);
  try
    // Check to see if Dispose has already been called.
    if not disposed then
    begin
      WriteLn('Destructor (dispose pattern) releases unmanaged resource wrapper''s resource');
      fileStream.Close;
      disposed := True
    end
  finally
    Monitor.Exit(Self)
  end
end;

procedure BaseResource.Close;
begin
  Free
end;

procedure BaseResource.DoSomething;
begin
  WriteLn('In BaseResource.DoSomething')
end;


C:Temp>dccil ResourceObjectEg.dpr
Borland Delphi Version 16.0
Copyright (c) 1983,2002 Borland Software Corporation
Confidential pre-release version built Nov 14 2002 17:05:31
ResourceObjectEg.dpr(75)
76 lines, 0.24 seconds, 9368 bytes code, 0 bytes data.

C:Temp>ResourceObjectEg
BaseResource constructor allocates unmanaged resource wrapper
In BaseResource.DoSomething
Destructor (dispose pattern) releases unmanaged resource wrapper's resource

C:Temp>

This code uses the Monitor class to ensure thread-safety, however this won't be necessary in the commercial release of Delphi for .NET. The compiler will auto-generate that code as well as the declaration and use of the already-called flag (disposed in the sample code above). That means that by the time the full product ships the destructor code will automatically be thread-safe, only execute once and will look like the following listing.

uses
  System.IO;

type
  BaseResource = class
  private
    // The unmanaged resource wrapper object
    fileStream: FileStream;
  public
    constructor Create(const fileName: String);
    destructor Destroy; override;
    procedure Close;
    procedure DoSomething;
  end;

constructor BaseResource.Create(const fileName: String);
begin
  inherited Create;
  WriteLn('BaseResource constructor allocates unmanaged resource wrapper');
  fileStream := System.IO.FileStream.Create(
    fileName, FileMode.Open, FileAccess.Read, FileShare.Read);
end;

destructor BaseResource.Destroy;
begin
  WriteLn('Destructor (dispose pattern) releases unmanaged resource wrapper''s resource');
  fileStream.Close;
end;

procedure BaseResource.Close;
begin
  Free
end;

procedure BaseResource.DoSomething;
begin
  WriteLn('In BaseResource.DoSomething')
end;

Well look at the Delphi for .NET syntax for using an object with a destructor shortly, but you should note the difference between a C# destructor and a Delphi destructor, which is of particular importance if you are going to be writing code in both languages. A C# destructor causes the compiler to auto-generate a finalizer, whilst a Delphi destructor (with the right signature) causes the compiler to implement the IDisposable interface.

The Borland R&D engineers were advised by the Microsoft CLR team not to have their compiler auto-generate finalizers in any volume since, whilst they are okay on a small hand-coded scale, on a large scale (such as when auto-generated by the compiler) finalizers can drag down the whole CLR system. To re-iterate a point from earlier, if you can achieve something without using a finalizer then do so.

The Dispose Pattern and Unmanaged Resources

The last section explored how to use the dispose pattern when your class deals with objects that wrap up the complexities of dealing with unmanaged objects, which is by far the most likely scenario when writing .NET code. This section looks at the case where your class directly uses an unmanaged resource, thereby requiring a finalizer, and sees what impact this has on the implementation of the dispose pattern.

This scenario is a bit more involved. If called, Dispose() must free up the unmanaged resources that are normally freed up by the finalizer (making the finalizer effectively redundant). Once it has done this it should also instruct the garbage collector not to call the finalizer. This is achieved by passing the object as a parameter to System.GC.SuppressFinalize() and makes the eventual garbage collection of the object that much more efficient (the object is never placed on the freachable queue).

Overloading Dispose()

Since the object's resources can now be freed either through the Dispose() method if it is called, or by the finalizer if not, a common implementation relies on using another version of the Dispose() method, this one being inaccessible to the object consumer and taking a Boolean parameter to indicate where it is being invoked from. This new Dispose() helper method should be protected and virtual so descendant classes can extend its behaviour.

The common approach is that when the public, parameterless Dispose() method calls this protected version, it passes true indicating the disposal is being instigated by user code. This means that it is safe to access finalizable objects referenced by data fields since their finalizers wont have been called yet, as well as the unmanaged resources. When the finalizer calls Dispose() it passes false, to indicate that it is being invoked through the CLRs finalizer thread, and so only the unmanaged resources owned by the object can be freed.

Here is a simple C# class that shows a typical implementation:

class BaseResource: IDisposable
{
    // Track whether Dispose has been called.
    private bool disposed = false;

    // The resource handle
    private IntPtr handle = IntPtr.Zero;

    // Class constructor
    public BaseResource()
    {
        Console.WriteLine("BaseResource constructor should have code to allocate the resource");
    }

    // Class destructor
    ~BaseResource()
    {
        Console.WriteLine("BaseResource destructor (finalizer) frees the resource");
        Dispose(false);
    }

    // Implement IDisposable and ensure IDisposable.Dispose is not virtual
    public void Dispose()
    {
        Console.WriteLine("Dispose pattern used to free the resource");
        Dispose(true);
        // Dispose called by programmer so prevent GC executing the finalizer
        GC.SuppressFinalize(this);
    }

    public void Close()
    {
        Dispose();
    }

    [DllImport("kernel32.dll")]
    static extern bool CloseHandle(IntPtr hObject);
    
    protected virtual void Dispose(bool disposing)
    {
        // Check to see if Dispose has already been called.
        if (!disposed)
        {
            if(disposing)
            {
                Console.WriteLine("Free managed resources");
            }
            Console.WriteLine("Free unmanaged resources");
            if (handle != IntPtr.Zero)
            {
                CloseHandle(handle);
                Console.WriteLine("Reset handle to safe value");
                handle = IntPtr.Zero;
            }
            disposed = true;         
        }
    }

    public void DoSomething()
    {
        Console.WriteLine("In BaseResource.DoSomething");
    }
}


C:Temp>csc ResourceObjectEg.cs
Microsoft (R) Visual C# .NET Compiler version 7.00.9466
for Microsoft (R) .NET Framework version 1.0.3705
Copyright (C) Microsoft Corporation 2001. All rights reserved.


C:Temp>ResourceObjectEg
BaseResource constructor should have code to allocate the resource
In BaseResource.DoSomething
Dispose pattern used to free the resource
Free managed resources
Free unmanaged resources

C:Temp>

As mentioned above, when you implement a method of an interface that is not declared with the virtual modifier, the C# compiler will treat it as both virtual and sealed so you do not need to use the sealed modifier on the method. This can be verified with IL DASM, by examining the CIL generated for the Dispose() method in the code above. Note the occurrence of the CIL virtual and final directives in the listing:

C# Dispose Code

Here is the same class above expressed in Delphi for .NET:

type
  BaseResource = class(System.Object, IDisposable)
  private
    // Track whether Dispose has been called.
    disposed: Boolean;
    // The resource handle
    handle: IntPtr;
  protected
    procedure Finalize; override;
    procedure Dispose(disposing: Boolean); overload; virtual;
  public
    constructor Create;
    // Implement IDisposable and ensure IDisposable.Dispose is not overridable
    procedure Dispose; overload;
    procedure Close;
    procedure DoSomething;
  end;

constructor BaseResource.Create;
begin
  inherited Create;
  handle := IntPtr.Zero;
  WriteLn('BaseResource constructor should have code to allocate the resource');
end;

procedure BaseResource.Finalize;
begin
  try
    WriteLn('BaseResource finalizer frees the resource');
    Dispose(False);
  finally
    inherited
  end
end;

procedure BaseResource.Dispose;
begin
  WriteLn('Dispose pattern used to free the resource');
  Dispose(True);
  // Dispose called by programmer so prevent GC executing the finalizer
  GC.SuppressFinalize(Self);
end;

procedure BaseResource.Close;
begin
  Dispose
end;

procedure BaseResource.Dispose(disposing: Boolean);
begin
  // Check to see if Dispose has already been called.
  if not disposed then
  begin
    if disposing then
    begin
      WriteLn('Free managed resources');
    end;
    WriteLn('Free unmanaged resources');
    if handle <> IntPtr.Zero then
    begin
      CloseHandle(handle.ToInt32);
      WriteLn('Reset handle to safe value');
      handle := IntPtr.Zero;
    end;
    disposed := True;
  end
end;

procedure BaseResource.DoSomething;
begin
  WriteLn('In BaseResource.DoSomething')
end;

C:Temp>dccil ResourceObjectEg.dpr
Borland Delphi Version 16.0
Copyright (c) 1983,2002 Borland Software Corporation
Confidential pre-release version built Nov 14 2002 17:05:31
ResourceObjectEg.dpr(6) Warning: Unit 'Borland.Win32.Windows' is experimental
ResourceObjectEg.dpr(100)
101 lines, 0.16 seconds, 10528 bytes code, 0 bytes data.

C:Temp>ResourceObjectEg
BaseResource constructor should have code to allocate the resource
In BaseResource.DoSomething
Dispose pattern used to free the resource
Free managed resources
Free unmanaged resources

C:Temp>

Again, as mentioned above, when you implement a method of an interface that is not declared with the virtual directive, the Delphi for .NET compiler will treat it as virtual and final so you do not need to use the final directive on the method. IL DASM verifies this:

Delphi dispose code

These implementations are typical of those that you find in .NET programming tutorials, but they are not thread-safe. Another thread could start disposing the object after the managed resources are disposed, but before the internal disposed field is set to true. The following modification to the protected Dispose() method remedies this by locking the object during the Dispose method to prevent any other threads calling Dispose().

Here it is in C#:

protected virtual void Dispose(bool disposing)
{
    // Make this routine thread-safe 
    lock(this)
    {
        // Check to see if Dispose has already been called.
        if (!disposed)
        {
            if(disposing)
            {
                Console.WriteLine("Free managed resources");
            }
            Console.WriteLine("Free unmanaged resources");
            if (handle != IntPtr.Zero)
            {
                CloseHandle(handle);
                Console.WriteLine("Reset handle to safe value");
                handle = IntPtr.Zero;
            }
            disposed = true;         
        }
    }
}

And here is the equivalent Delphi for .NET code:

uses
  System.Threading,
  Borland.Win32.Windows;

procedure BaseResource.Dispose(disposing: Boolean);
begin
  // Make this routine thread-safe
  Monitor.Enter(Self);
  try
    // Check to see if Dispose has already been called.
    if not disposed then
    begin
      if disposing then
      begin
        WriteLn('Free managed resources');
      end;
      WriteLn('Free unmanaged resources');
      if handle <> IntPtr.Zero then
      begin
        CloseHandle(handle.ToInt32);
        WriteLn('Reset handle to safe value');
        handle := IntPtr.Zero;
      end;
      disposed := True;
    end
  finally
    Monitor.Exit(Self)
  end
end;

We saw the special Delphi destructor pattern earlier, which is translated into a silent implementation of IDisposable for you. It should be made clear here that this pattern is not applicable when you have a finalizer to implement. Borland's recommended coding style is to not mix traditional destructors with CLR finalizers. If you need a finalizer in your class, you should implement IDisposable yourself completely, as we have done here. Do not mix finalizers with the special destructor Destroy, as this mixture is not guranteed to work in the future as the destructor implementation is tuned.

It is currently possible to break this guideline, but you do not gain anything by doing so. In the future the compiler may well prohibit the implementation of a finalizer in combination with the special destructor pattern.

Post-Dispose() Considerations

We've seen quite a bit of information about the dispose pattern now, but we are not quite done with it yet. Whilst Dispose() must be callable multiple times, without throwing any exceptions, once the object has been finalized (either through the finalizer or through a call to Dispose()) it should be considered unusable since its key resources have been released. To enforce this unusability after a call to Dispose(), it is expected that normal methods through throw a System.ObjectDisposedException in these cases.

The trivial DoSomething() method in our class should look like this in C#:

public void DoSomething()
{
    if (disposed)
        throw new ObjectDisposedException(ToString());
    Console.WriteLine("In BaseResource.DoSomething");
}

And like this in Delphi for .NET:

procedure BaseResource.DoSomething;
begin
  if disposed then
    raise ObjectDisposedException.Create(ToString);
  WriteLn('In BaseResource.DoSomething')
end;

Using a Disposable Object

In C# you might use an object that implements the dispose pattern with code that looks like this:

class MainApp
{
    [STAThread]
    static void Main()
    {
        BaseResource br = new BaseResource();
        try
        {
            br.DoSomething();
        }
        finally
        {
            if (br != null)
                br.Dispose();
        }
    }
}

This generally works just fine, however some objects don't make their Dispose() methods available to object consumers (such as the FileStream class). In such cases you can call the Close() method if you wish, but if you want some consistency across your use of objects that implement the dispose pattern you could talk to the IDisposable interface directly:

class MainApp
{
    [STAThread]
    static void Main()
    {
        BaseResource br = new BaseResource();
        try
        {
            br.DoSomething();
        }
        finally
        {
            if (br != null)
                ((IDisposable) br).Dispose();
        }
    }
}

However C# offers the using statement (more generally used to reference other namespaces), which allows you to abbreviate your use of a disposable object to look like this:

class MainApp
{
    [STAThread]
    static void Main()
    {
        using (BaseResource br = new BaseResource())
        {
            br.DoSomething();
        }
    }
}

In Delphi for .NET you might use an object that implements the dispose pattern with code that looks like this:

var
  BR: BaseResource;
begin
  BR := BaseResource.Create;
  try
    BR.DoSomething
  finally
    if Assigned(BR) then
      BR.Dispose
  end
end.

Again, you may run into objects that do not expose the Dispose()method directly, so you might need to access it via the interface:

var
  BR: BaseResource;
begin
  BR := BaseResource.Create;
  try
    BR.DoSomething
  finally
    if Assigned(BR) then
      (BR as IDisposable).Dispose
  end
end.

However thanks to the way TObject.Free() has been implemented, you can also make use of a disposable object like this:

var
  BR: BaseResource;
begin
  BR := BaseResource.Create;
  try
    BR.DoSomething
  finally
    BR.Free
  end
end.

This effect is designed to help Delphi developers port their code over to the .NET platform without rewriting endless calls to Free(). Note that if you port some code to .NET and find that you have no need to do any finalization (all the destructor did was free objects, that didn't control unmanaged resources), and so you remove the destructor (or use conditional compilation to prevent it being compiled), you can still use Free() without an issue. It may involve a little overhead but it should be negligible.

Summary

This article has had a close-up look at destructors and finalizers, in relation to the garbage collector that now takes the responsibility for reclaiming the memory occupied by objects that are no longer in use. There are many details to take on board, but what you should take away after reading it are the following points:

  • Dont implement a finalizer (or destructor in C#) unless you have unmanaged resources to release. Remember that any objects referenced by your object do not need freeing; the garbage collector will do that.
  • Wherever possible use existing .NET classes to access unmanaged resources (such as file handles, socket handles, window handles or database connections) rather than implementing a finalizer in a new class.
  • Finalizers do not execute in any prompt manner nor in any predictable order, they add overhead to object construction and to garbage collection and they cause your objects to exist in memory much longer than you might expect (which makes any objects referenced by the finalizable object remain in existence much longer than you'd expect).
  • Implement the dispose pattern to allow object consumers to free up any unmanaged resources that you directly or indirectly use. If you have a finalizer, this will make the reclamation of your object and its resources able to be more efficient
  • If you implement the dispose pattern, be sure to implement your methods to throw a System.ObjectDisposedException exception after disposal to avoid your objects being used after their time is over.
  • If you implement the dispose pattern and also a finalizer, ensure that the Dispose() method calls GC.SuppressFinalize().
  • Follow one of the patterns of implementation for the dispose pattern as developed above, depending on the type of class you are implementing.
  • If you plan to use the implicitly implemented dispose pattern in Delphi for .NET objects, be sure you do not implement a finalizer as well (if you need a finalizer, implement IDisposable yourself).

Further Reading

  1. Garbage Collection: Automatic Memory Management in the Microsoft .NET Framework, Jeffrey Richter, MSDN Magazine, November 2000.
    This is the first part of the definitive article on garbage collection, which discusses how resources are allocated and managed, how garbage collection works and looks at object finalizers.
  2. Garbage Collection-Part 2: Automatic Memory Management in the Microsoft .NET Framework, Jeffrey Richter, MSDN Magazine, December 2000.
    This is second part of the definitive article on garbage collection, which discusses strong and weak object references, generations, how to control and monitor the garbage collector and how it works with multi-threaded applications.
  3. Applied Microsoft. .NET Framework Programming, Jeffrey Richter, Microsoft Press, 2002.
    Chapter 19 of this book, Automatic Memory Management (Garbage Collection), is a later version of the two-part article listed above.

Acknowledgements

Thanks are due to Danny Thorpe, Guy Smith-Ferrier, Hallvard Vassbotn, Dave Jewell and Roy Nelson for helpful contributions to the accuracy and readability of this article. Thanks also to Matt Davey for the update to the GC generation initial sizes.

About Brian Long

Brian Long used to work at Borland UK, performing a number of duties including Technical Support on all the programming tools. Since leaving in 1995, Brian has been providing training and consultancy on Borland's RAD products ever since, and is now moving into the .NET world.

Besides authoring a Borland Pascal problem-solving book published in 1994, Brian is a regular columnist in The Delphi Magazine and has had numerous articles published in Developer's Review, Computing, Delphi Developer's Journal and EXE Magazine. He was nominated for the Spirit of Delphi 2000 award and was voted Best Speaker at Borland's BorCon 2002 conference in Anaheim, California by the conference delegates.

There are a growing number of conference papers and articles available on Brian's Web site, so feel free to have a browse.

In his spare time (and waiting for his C++ programs to compile) Brian has learnt the art of juggling and making inflatable origami paper frogs.



Server Response from: ETNASC03