4.8. Mastering Finalize Semantics: IDisposable

Garbage collection solves an important problem: that of automatically managing the allocation and freeing up objects on the heap. But that solution introduces the smaller problem of deterministic finalization. What this means in a very broad sense is that we cannot predict when (or if) an object is finalized by the garbage collector.

One case in which the time of finalization matters occurs when a class object acquires unmanaged resources, such as a window or file handle or a database connection—resources that we don't want tied up too long, or at least no longer than necessary. What we would like, ideally, is to have such a resource freed automatically after a last use of the object holding it. Unfortunately, we cannot automate that—at least in a timely manner. The reason is that there is no deterministic finalization under .NET.

The garbage collector has knowledge of the memory available in the managed heap. When it determines that the available memory is below a particular threshold, it performs a collection. Until then, an unreferenced object continues to exist in memory.

The garbage collector has no knowledge of external resources such as database connections. If the availability of database connections is low, there is no equivalent mechanism to automatically perform a collection.

An unreferenced object maintaining a database connection, for example, continues to hold onto that connection until either the garbage collector finalizes the object or the user explicitly frees the resource. This is the problem that the IDisposable interface is intended to manage.

A class that is expected to acquire unmanaged resources should implement IDisposable. IDisposable declares a single member: Dispose(). All resources held both by the object and by any object contained within this object should be freed up within this implementation of Dispose().

How is Dispose() invoked? In most cases, the user must manually invoke it after a last use of the object. In case the user forgets, we also provide a destructor within which Dispose() is called. If the user manually invokes Dispose(), we disable the destructor instance through a call of the SuppressFinalize() member function of the GC garbage collection class. For example, :

class ResourceWrapper : IDisposable
{
    // ...
    public void Dispose()
    {
        Dispose( true );

        // take us off of the finalization queue
        GC.SuppressFinalize( this );
    }

    protected virtual void Dispose( bool disposing )
    {
        if ( disposing ) {
            // dispose of managed resources ...
        }

        // dispose of unmanaged resources ...
    }

    // just in case Dispose() is not explicitly invoked
    ~ResourceWrapper() { Dispose(false); }
}

Optionally we may wish to maintain an IsDisposed class member that indicates whether Dispose() has yet been invoked on the object. This allows us to avoid attempting to free the same resources multiple times. It also allows us to trap an attempt to use an object on which Dispose() has been invoked. In the latter case we are advised to throw an ObjectDisposedException object.

When we program under .NET, it is important that we always be aware of the possibility of an exception being thrown. For example, the following code is not exception safe:

foo()
{
       FileStream fin = new
             FileStream(@"c:fictionsalice.txt", FileMode.Open);
       StreamReader ifile = new StreamReader( fin );

       while (( str = ifile.ReadLine()) != null )
       {
             // ...
       }

       ifile.Close();
}

If ReadLine() throws an exception, the Close() method is never invoked; the file handle remains open until ifile is collected. In this case Close() wraps the invocation of Dispose().

A safer but more complex implementation introduces a finally clause to guarantee the invocation of either Close() or Dispose() whether or not an exception occurs:

foo()
{
       FileStream fin = new
             FileStream(@"c:fictionsalice.txt", FileMode.Open);
       StreamReader ifile = new StreamReader( fin );

       try
       {
           while (( str = ifile.ReadLine()) != null )
                        // ...
       }
       finally
         { ifile.Close(); }

}

A special instance of the using statement provides a shorthand notation for the invocation of an object's Dispose() method—for example:

foo()
{
    // equivalent to the earlier try/finally block
    using ( File f = new File( "c:	mp" ))
          { byte[] b = f.Read(); }
}

This using statement expands internally into the try/finally block of code explicitly programmed in the previous example.

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

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