C# provides
garbage collection and thus does not need an explicit destructor. If
you do control an unmanaged resource, however, you will need to
explicitly free that resource when you are done with it. Implicit
control over this resource is provided with a Finalize( )
method (called a
finalizer
), which will be called by the
garbage collector when your object
is destroyed.
The finalizer should only release resources that your object holds on
to, and should not reference other objects. Note that if you have
only managed references you do not need to and should not implement
the Finalize()
method; you want this only for
handling unmanaged resources. Because there is some cost to having a
finalizer, you ought to implement this only on methods that require
it (that is, methods that consume valuable unmanaged resources).
You must never call an object’s Finalize( )
method directly (except that you can call the base class’
Finalize( )
method in your own Finalize( )
). The garbage collector will call Finalize( )
for you.
C#’s destructor looks, syntactically, much like a C++ destructor, but it behaves quite differently. You declare a C# destructor with a tilde as follows:
~MyClass( ){}
In C#, however, this syntax is simply a shortcut for declaring a
Finalize( )
method that chains up to its base
class. Thus, writing:
~MyClass( ) { // do work here }
is identical to writing:
MyClass.Finalize( ) { // do work here base.Finalize( ); }
Because of the potential for confusion, it is recommended that you eschew the destructor and write an explicit finalizer if needed.
It is not legal to call a finalizer explicitly.
Your
Finalize( )
method
will be called by the garbage collector. If you do handle precious
unmanaged resources (such as file handles) that you want to close and
dispose of as quickly as possible, you ought to implement the
IDisposable
interface. (You will learn more about
interfaces in Chapter 8.) The
IDisposable
interface requires its implementers to
define one method, named Dispose( )
, to perform
whatever cleanup you consider to be crucial. The availability of
Dispose( )
is a way for your clients to say
“don’t wait for Finalize( )
to be
called, do it right now.”
If you provide a Dispose( )
method, you should
stop the garbage collector from calling Finalize( )
on your object. To stop the garbage collector, you call
the static method GC.SuppressFinalize()
, passing
in the this
pointer for your object. Your
Finalize( )
method can then call your
Dispose( )
method. Thus, you might write:
public void Dispose( ) { // perform clean up // tell the GC not to finailze GC.SuppressFinalize(this); } public override void Finalize( ) { Dispose( ); base.Finalize( ); }
For some objects, you’d rather have your clients call the
Close( )
method. (For example,
Close
makes more sense than Dispose( )
for file objects.) You can implement this by creating a
private Dispose( )
method and a public
Close( )
method and having your Close( )
method invoke Dispose( )
.
Because you cannot be certain that your user will call
Dispose( )
reliably, and because finalization is
nondeterministic (i.e., you can’t control when the GC will
run), C# provides a
using
statement
which ensures that Dispose( )
will be called at
the earliest possible time. The idiom is to declare which objects you
are using and then to create a scope for these objects with curly
braces. When the close brace is reached, the Dispose( )
method will be called on the object automatically, as
illustrated in Example 4-6.
Example 4-6. The using construct
using System.Drawing; class Tester { public static void Main( ) { using (Font theFont = new Font("Arial", 10.0f)) { // use theFont } // compiler will call Dispose on theFont Font anotherFont = new Font("Courier",12.0f); using (anotherFont) { // use anotherFont } // compiler calls Dispose on anotherFont } }
In the first part of this example, the
Font
object is created within the
using
statement. When the using
statement ends, Dispose( )
is called on the
Font
object.
In the second part of the example, a Font
object
is created outside of the using
statement. When we
decide to use that font, we put it inside the
using
statement and when that statement ends, once
again Dispose( )
is called.
The using
statement also protects you against
unanticipated exceptions. No matter how control leaves the
using
statement, Dispose( )
is
called. It is as if there were an implicit
try-catch-finally
block. (See Section 11.2 in Chapter 11
for details.)
Get Programming C# now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.