Finalizing and Disposing

Finalization allows an object to release unmanaged resources it’s using before it’s collected. The .NET Object class defines the following method signature for this purpose:

protected override void Finalize();

During a collection run, the GC places all unreferenced objects in a finalize queue. This queue is then scanned, and the memory occupied by classes that don’t define a Finalize method is released. Objects that do define a Finalize method are placed on the freachable (pronounced F-Reachable) queue. A dedicated thread responsible for calling Finalize methods sleeps when no objects are in the freachable queue. When objects are added to the queue, the thread is awakened and calls the Finalize method.

Caution

A dedicated thread calls the Finalize method. Code in the Finalize method can’t make assumptions about the state of the thread. For example, local Thread variables won’t be available.

Destructors

While the .NET Framework defines the Finalize method, C# doesn’t allow classes to override it. Instead, classes that need to clean up resources before collection must implement a destructor. Here’s a simple example of a class that defines a destructor:

public class Destructor {

    public Destructor() {
        Console.WriteLine("Instantiated");
    }

    ~Destructor() {
        Console.WriteLine("Destructor called");
    }
}

The destructor is marked in boldface and takes the form of a constructor with a tilde (~) prepended; destructors take no arguments and return no value. The destructor is implicitly translated to the following code:

protected override void Finalize() {
    try {
        Console.WriteLine("Destructor called");
    } finally {
        base.Finalize();
    }
}

For the following reasons, it’s advisable to avoid destructors when designing classes:

  • Objects with a destructor get promoted to older generations, increasing the pressure on memory allocation and preventing the memory held by the object from being released immediately. Generations are discussed in the Controlling the Garbage Collector section earlier in this appendix.

  • Calling a destructor on an object takes time. Forcing the GC to call the destructors for a large number of objects can affect performance.

  • The garbage collector doesn’t guarantee when a destructor will be called; objects will hold resources until the method is called, which can put pressure on system memory.

  • A destructor might not be called when an application exits. To allow a process to terminate quickly, destructors aren’t called on unreachable objects, and objects that are used by background threads or were created during the termination phase won’t be finalized.

  • The garbage collector doesn’t guarantee the order in which objects will be finalized. References with destructors that refer to inner objects with destructors can cause unpredictable results.

When using destructors, all exceptions must be caught. If an exception is propagated outside the destructor, the finalizer thread assumes that finalization has completed and not all resources will be freed.

Resurrection

The freachable queue gets it name because objects contained in the queue are considered to be reachable (and the f stands for finalization). The queue is a root, and objects referenced by the queue aren’t garbage-collected; objects remain in the queue until the destructor is called, at which point they are removed from the queue and are available for garbage collection.

There is a technique called resurrection, whereby a reference to an object is created from within the object’s destructor, preventing it from being garbage-collected. Here’s an example of how to resurrect an object:

public class MyObject {

    ~MyObject() {
        Holder.s_holder = this;
        GC.ReRegisterForFinalize(this);
    }
}

class Holder {
    public static object s_holder;
}

In the destructor, a reference to MyObject is created from the static object in the Holder class. When the finalizer thread has called this method, the object is placed on the finalizer queue, but because there’s a valid reference from the Holder class, it won’t be collected.

Calling GC.ReRegisterForFinalize ensures that the destructor will be called again if the reference in Holder is set to null and the object is collected. Resurrection rarely represents good practice and should be used with caution.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset