Sunday, March 25, 2012

Implementing IDisposable Interface Part - II

In previous post we discussed implementing IDisposable interface for unmanaged code. Even though your class may implement IDisposable interface and you release the resources, but what if a developer never calls the dispose method? Since GC can't free up resources claimed by your unmanaged code and if you forgot to call Dispose method, you are back to square one.

What if we somehow force Garbage Collector to call the Dispose method for our unmanaged code? This will be best of both worlds - you can free up resources as soon as you are done, but if you forgot to free up resources, you can be sure that GC at some point will reclaim those resources. In order to force GC to free up unmanaged resources, you have to implement your own version of Finalize method. GC will use this finalize method to destroy your objects. Declaring a finalize method in C# is similar to destructor method you create in C++

Lets extend the class we created in previous post.


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;

namespace IDisposableInterface
{
    class Unmanaged:IDisposable
    
    {
        private bool _disposed;
        private IntPtr _handle;

        //initializer method 
        public Unmanaged()
    {
        //we create a new pointer and associate it with the current process
        // This is unmanaged code and _handle must be disposed explicity 
        //and GC will not collect it
         _handle = new IntPtr();
        _handle = System.Diagnostics.Process.GetCurrentProcess().Handle;
    }
        public IntPtr Handle
        {
            get { return _handle; }
        }
      //notice we have declared this method as private because this is not thread      -safe 
      //and should only be called once. Here we are declaring a global variable        //to ensure it only executes once.
       private void Dispose(bool disposing)
        {
            if (!_disposed)
            {
                if (disposing)
                //release unmanaged resources
                 _handle = IntPtr.Zero;
                _disposed = true;
            }
        }
        public void Dispose()
        {
            Dispose(true);
        }
       //Create Finalize Method. This will be called by GC to dispose off 
       //unmanaged resources.
       ~Unmanaged()
       {
          Dispose(true);
       }
    }
}

While this works, but you could be destroying an object twice and may result in an error. Even if it doesn't result in an error, you are still unnecessarily calling Dispose more than once. What if you can design it in a way that GC only calls your dispose method only when you haven't called it? You can do this by modifying your Dispose method and adding SuppressFinalize.

public void Dispose()
        {
            Dispose(true);
            //don't bother trying to dispose this object.
            GC.SuppressFinalize(this); 
        }

Now, if you have called the Dispose method yourself, you have directed GC to don't bother disposing this object. If you didn't call Dispose method, GC will take care of it.

In reality, most classes that have some unmanaged code almost always have the managed code as well. Wouldn't it be nice to dispose off your managed resources when you destroy your unmanaged resources as well? GC will eventually destroy your managed resources, but why wait when you know you don't need them anymore? Especially if they consume large amount of memory. Let's modify the above class and create a handle to read a file from the hard-drive.


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;

namespace IDisposableInterface
{
    class Unmanaged:IDisposable
    
    {
        private bool _disposed;
        private IntPtr _handle;
        private System.IO.Stream _filestream;   //managed resource
        //initializer method 
        public Unmanaged()
    {
        //we create a new pointer and associate it with the current process
        // This is unmanaged code and _handle must be disposed explicity 
        //and GC will not collect it
         _handle = new IntPtr();
        _handle = System.Diagnostics.Process.GetCurrentProcess().Handle;
        _filestream = System.IO.File.Open(@"c:\temp\TestFile.txt", 
                                          System.IO.FileMode.Open);
    }
        public IntPtr Handle
        {
            get { return _handle; }
        }
        public long FileSize
        {
            get { return _filestream.Length; }
        }
      //notice we have declared this method as private because this is not thread      -safe 
      //and should only be called once. Here we are declaring a global variable        //to ensure it only executes once.
       private void Dispose(bool disposing)
        {
            if (!_disposed)
            {
                if (disposing)
                 {
                    //release managed resources
                    if (_filestream !=null) _filestream.Dispose();
                 }
                 //release unmanaged resources
                 _handle = IntPtr.Zero;
                _disposed = true;
            }
        }
        public void Dispose()
        {
            Dispose(true);
            //don't bother trying to dispose this object.
            GC.SuppressFinalize(this);
        }
       //Create Finalize Method. This will be called by GC to dispose off 
       //unmanaged resources.
       ~Unmanaged()
       {
          Dispose(false);
       }
    }
}

Notice few things that I changed in this class. First, I created a _fileStream object which is a managed resource. Then in Dispose method, I disposed this object along with unmanaged resource i.e. IntPtr. But I also did something else, which is very important when you are disposing managed resources yourself. In ~Unamanged destructor, I am now passing "false" for disposing. Since GC runs in background and doesn't destroy objects in order they were created, it is entirely possible that _filestream object was already destroyed before GC called your version of Finalize method (destructor). You want to make sure that when you manually call Dispose, you dispose off both managed and unmanaged resources (GC couldn't dispose off managed resource then because object is still in use), but if you let GC call your destructor, you shouldn't dispose managed resources since GC may have already disposed it.

Even if you are using managed resources, it is a good practice to dispose off your objects especially if they are large and consume lot of memory (such as collection of images) as soon as your are done using it. But, you must be careful to design your dispose methods in such a way that you don't interfere with GC.

Thank you.

No comments:

Post a Comment