4.4 Destroying Objects

Since C# provides garbage collection, you never need to explicitly destroy your objects. However, if your object controls unmanaged resources, you will need to explicitly free those resources when you are done with them. Implicit control over unmanaged resources is provided by a destructor, which will be called by the garbage collector when your object is destroyed.

C and C++ programmers take note: A destructor is not necessarily called when an object goes out of scope but when it is garbage-collected (which may happen much later). This is known as non-deterministic finalization.

The destructor 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 a destructor; you want this only for handling unmanaged resources. Because there is some cost to having a destructor, you ought to implement this only on methods that require it (that is, methods that consume valuable unmanaged resources).

Never call an object's destructor directly. The garbage collector (GC) will call it for you.

How Destructors Work

The garbage collector maintains a list of objects that have a destructor. This list is updated every time such an object is created or destroyed.

When an object on this list is first collected, it is placed on a queue with other objects waiting to be destroyed. After the destructor executes, the garbage collector then collects the object and updates the queue, as well as its list of destructible objects.

4.4.1 The C# Destructor

C#'s destructor looks, syntactically, much like a C++ destructor, but it behaves quite differently. Declare a C# destructor with a tilde as follows:

~MyClass( ){}

In C#, this syntax is simply a shortcut for declaring a Finalize( ) method that chains up to its base class. Thus, when you write:

~MyClass( ) {     // do work here }

the C# compiler translates it to:

protected override void Finalize( ) {    try    {       // do work here.    }    finally    {       base.Finalize( );    } }

Java programmers take note: Unlike in Java, the call to Finalize( ) in C# is actually guaranteed to happen unless the programmer explicitly permits it to be skipped.

4.4.2 Destructors Versus Dispose

It is not legal to call a destructor explicitly. Your destructor 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 the destructor to be called, do it right now."

If you provide a Dispose( ) method, you should stop the garbage collector from calling your object's destructor. To do so, call the static method GC.SuppressFinalize( ), passing in the this pointer for your object. Your destructor can then call your Dispose( ) method. Thus, you might write:

using System; class Testing : IDisposable {   bool is_disposed = false;   protected virtual void Dispose(bool disposing)   {     if (!is_disposed) // only dispose once!     {       if (disposing)       {         Console.WriteLine("Not in destructor, OK to reference other objects");       }       // perform cleanup for this object       Console.WriteLine("Disposing...");     }     this.is_disposed = true;   }      public void Dispose( )   {     Dispose(true);     // tell the GC not to finalize     GC.SuppressFinalize(this);   }      ~Testing( )   {     Dispose(false);     Console.WriteLine("In destructor.");   } }

4.4.3 Implementing the Close( ) Method

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

4.4.4 The using Statement

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 that ensures that Dispose( ) will be called at the earliest possible time. The idiom is to declare the 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 statement
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; when that statement ends, Dispose( ) is called once again.

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 Chapter 11 for details.)



Programming C#
C# Programming: From Problem Analysis to Program Design
ISBN: 1423901460
EAN: 2147483647
Year: 2003
Pages: 182
Authors: Barbara Doyle

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net