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:
- free resources used by the object
- 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):
- 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
- 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:

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:

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
- 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.
- 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.
- 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.