Labels

Tuesday, March 31, 2009

IDisposable & Dispose Myth


IDisposable & Dispose Myth

Why Required – Even when GC is there ??

The garbage collector automatically releases the memory allocated to a Managed Object when that object is no longer used, though, it is unpredictable when garbage collection will occur.
But , the garbage collector has no knowledge of Unmanaged Resources.

Unmanaged Resources are –

-          Window handles,
-          File handles - open files and streams
-          Network connections, and
-          Database connections.

When managed classes encapsulate direct or indirect references to unmanaged resources, you need to Make Special Provision To Ensure The Unmanaged Resources Are Released when an instance of the class is garbage collected.

The mechanisms are:

-          Declaring a destructor (or finalizer) as a member of your class.
-          Implementing the System.IDisposable interface in your class.

Destructor Approach –

It has two disadvantages –

-          Undeterministic - C# destructors are used far less than their C++ equivalents. The problem with C# destructors when compared with their C++ counterparts is that they are non deterministic. When a C++ object is destroyed, its destructor runs immediately. However, because of the way the garbage collector works, there is no way to know when an object’s destructor will actually execute.

-          Requires two Pass –
o        Objects that do not have a destructor get removed from memory in one pass of the garbage collector.
o        But objects that have destructors require two passes to be destroyed: the first one calls the destructor and move the object to the finalization queue without removing the object, the second actually deletes the object.



-          Here when garbage collector runs for the first time it searches for objects whose memory has to freed.
-          He sees three objects but only cleans the memory for Object1 and Object3.
-          Object2 it pushes to the finalization queue.
-          Now garbage collector runs for the second time. He see’s there are no objects to be freed and then checks for the finalization queue and at this moment it clears object2 from the memory.

So if you notice that object2 was freed from memory in the second round and not first


IDisposable Approach –

Note -
-          While implementing Dispose() method of the IDisposable Interface – Make sure that the method is not  decalred to be virtual. A derived class should not be able to override this method.
-          Dispose is required to be called explicitly. Or it can be called automatically if used in the ‘Using’ block.

Imp – Use of ‘Using ‘ block requires the Object to implement ‘IDisposable’ interface.



public class MyResource : IDisposable
    {
        private IntPtr handle;  // Unmanaged resource.
        private Component component = new Component(); // Managed resource             
        private bool disposed = false; // Track whether Dispose has been called.

        public MyResource(IntPtr handle)
        {
            this.handle = handle;
        }

        // Do not make this method virtual. A derived class should not be able to override this method.
        public void Dispose()  // IDisposable Implementation
        {
            MyDispose (true);
            GC.SuppressFinalize(this);
        }

        private void MyDispose(bool thruDispose)
        {           
            if (!this.disposed) // Check to see if Dispose has already been called.
            {
                // If disposing equals true, dispose all Managed and Unmanaged resources.
                if (thruDispose)
                {
                    component.Dispose(); // Dispose Managed resources.
                }

                // Now Cleanup Unmanaged resources. If disposing is false, only the following code is executed.
                CloseHandle(handle);
                handle = IntPtr.Zero;
            }
            disposed = true;  // Mark that Dispose has been called
        }

        // Use interop to call the method necessary  to clean up the unmanaged resource.
        [System.Runtime.InteropServices.DllImport("Kernel32")]
        private extern static Boolean CloseHandle(IntPtr handle);

        // This destructor will run only if the Dispose method does not get called.
        ~MyResource()
        {
            // Do not re-create Dispose clean-up code here.
           // Calling Dispose(false) is optimal in terms of readability and maintainability.
            MyDispose (false);
        }
    }



Usage – Explicit Approach

    Resource theInstance = null;
    try
    {
            theInstance = new Resource();
    }
    finally
    {
            if (theInstance != null)
            {
                theInstance.Dispose();  // Calling Explicity
            }
    }

Usage – Implicit Approach

using (Resource theInstance1 = new Resource())
{
  // Here the Dispose Will Be Called Automatically.
  // using requires the Object to implement 'IDispoasble'
}

Note – Even in the below code the Reosurce Object is created indirectly, the Dispose() will be called automatically,
          if the assignment is used in ‘using’ block as below.

class TestClass
    {
        public MyResource GetResource()
        {
            int x = 10;
            IntPtr ptr = new IntPtr(x);
            MyResource obj = new MyResource(ptr);
            return obj;
        }
    }
Usage – Implicit Approach

TestClass clsObject = new TestClass();
using (Resource theInstance1 = clsObject.GetResource())
{
  // Here also the Dispose Will Be Called Automatically. 
}



Why are we clearing both Managed & UnManaged both when the Dispose() is called explicitly by the consumer? Why not only UnManaged? i.e using MyDispose (true);

-          One reason is – In the Dispose() we are suppressing the Finalize/Destructor of the Resource class. Hence if we do not clear Managed resources then who will collect them.
-          Second - If a consumer calls IDisposable.Dispose(), then that consumer is indicating that all managed and unmanaged resources associated with that object should be cleaned up.

Why are we clearing only UnManaged when Destructor is called ? i.e using MyDispose (false);

-          If a destructor has been invoked, then all resources still need to be cleaned up. However, in this case, we know that the current DESTRUCTOR MUST HAVE BEEN CALLED BY THE GC and we should not attempt to access other managed objects because we can no longer be certain of their state. In this situation, the best we can do is clean up the known unmanaged resources, and Hope That any referenced managed objects also have destructors that will perform their own cleaning up.


-          Do not call Close or Dispose on a Connection, a DataReader, or any other managed object in the Finalize method of your class. In a finalizer, only release unmanaged resources that your class owns directly. If your class does not own any unmanaged resources, do not include a Finalize method in your class definition.




Check Class List that support Dispose() – Reference.
However for quick reference below objects support Dispose().

AsymmetricAlgorithm,BinaryReader ,BinaryWriter ,Brush ,CacheDependency ,Component ,ComponentDesigner ,Container ,Control ,CryptoAPITransform ,Cursor ,CustomLineCap ,DesignerTransaction ,EncoderParameter ,EncoderParameters ,EventHandlerList ,Font ,FontCollection ,FontFamily ,FromBase64Transform ,Graphics ,GraphicsPath ,GraphicsPathIterator ,HashAlgorithm ,HttpApplication ,Icon ,Image ,ImageAttributes,IsolatedStorageFile ,License ,LocalizationExtenderProvider ,ManagementObjectCollection ,MarshalByValueComponent ,Matrix ,MessageEnumerator ,MessageQueueEnumerator ,MessageQueueTransaction ,OdbcDataReader,OdbcTransaction ,OleDbDataReader ,OleDbTransaction ,OracleDataReader ,OracleTransaction ,PaintEventArgs ,Pen ,Region ,RegistryKey ,ResourceReader ,ResourceSet ,ResourceWriter ,ResXResourceReader ,ResXResourceWriter ,SearchResultCollection ,ServicedComponent ,Socket,SqlCeCommand ,SqlCeConnection ,SqlCeDataReader ,SqlCeEngine ,SqlCeRemoteDataAccess ,SqlCeReplication ,SqlCeTransaction ,SqlDataReader ,SqlTransaction ,Stream ,StringFormat ,SymmetricAlgorithm ,TcpClient ,TempFileCollection ,TemplateEditingVerb ,TextReader ,TextWriter ,Timer ,ToBase64Transform ,TraceListener ,UdpClient ,WaitHandle ,WebResponse

Why only these Objects support Dispose() and not others ??

The clue is these Objects mut have been using multiple Unmanaged resources internally (As in our example above). Now to clear all the referred unmanaged resources by these Objects, in one shot, Dispose is been supprted by these Objects.
The rest of the Objects must have not been using any Unmanaged resources internally i.e all the internally reference objects are Managed. Thus Dispose is not required to reclaim the memory as the GC can take care of the Managed Resources referenced by these Objects.

Is it Thread Safe –

This simplistic approach is not thread-safe and depends on the caller ensuring only one thread is calling the method concurrently.

Last Question – Why IDisposable is used to clear UnManaged Resources ??

-          In nut shell remember the fact that the GC does not take care of memory reclaimation for UnManagedResources. This is what individual component containing UnManaged resoures (like in our example) is required to handle.
-          Now to handle this, one can go for its own implentation. i.e In your application one can define thier own Interface like XDisposable and define a method, may be XDispose().
-          After this the way we implement IDisposable, use XDisposable and then implement the XDispose() in your class.
-          Once implemented use it the way you use IDisposable.

The only limitation would be – ‘Using’ block will not fire the XDispose() automatically.



public class MyResource : XDisposable
    {
        // Any Code
        public void XDispose()  // IDisposable Implementation
        {
            MyDispose (true);
            GC.SuppressFinalize(this);
        }

        private void MyDispose(bool thruDispose)
        {           
            // Code Here
        }

        ~MyResource()
        {
            MyDispose (false);
        }
    }
Usage – Explicit Approach

    Resource theInstance = null;
    try
    {
            theInstance = new Resource();
    }
    finally
    {
            if (theInstance != null)
            {
                theInstance.XDispose();  // Calling Explicity
            }
    }

Usage – Implicit Approach  - This will not work

using (Resource theInstance1 = new Resource())
{
  // Here the XDispose() Will Not Be Called Automatically.
 
}




Regards,
Arun Manglick



No comments:

Post a Comment