Destroying Objects


Part of the lifecycle of an object involves the object being destroyed by the runtime and the memory being reclaimed; although this process is automatic we still have a surprising level of control over this. The destruction of an object can be done explicitly by setting the object reference to null and taking care that there are no other references to the object, or it can be done implicitly by the runtime if the object becomes out-of-scope – in either case, the reference count will be amended and memory can be freed by the garbage collector when there are no more references.

The GC is smart enough to realize that it shouldn't be compacting and freeing memory at all if the object count within an application is so low that it doesn't need the GC to free memory, and new objects are created very infrequently. The object destruction process is carried out by the GC, and depends entirely on application memory availability and need.

There are mechanisms to make objects responsible for their own de-referencing of internal objects when they are destroyed, which is extremely useful for releasing handles to underlying resources that the CLR doesn't manage. Whenever the GC reclaims the memory occupied by an object it will invoke its Finalize() method, which should release any resources that are not managed by the CLR. You can think of this method as an interception by the runtime, which will stop the GC in its tracks and invoke code before the object is destroyed and memory de-allocated.

Note

Java developers will be familiar with this syntax, as the same mechanism has been written into the Java language and the JVM since the JDK 1.0.

We can provide our own implementation of this method by overriding the implementation of the base class method from System.Object. For all .NET languages, we can generally use the Finalize() method by overriding the base class method of the same name and signature – except in C# and C++. The Finalize() method used to be used in place of destructors in C# throughout early beta releases of the .NET Framework SDK, but is now deprecated.

The concept of the Finalize() method is the same for C# and Managed C++, except we use a different syntax. C# uses a syntax known as a destructor to clean up resources – these resources should typically be unmanaged resources such as file handles and database connections. For example, in our Employee class, if we had created a database connection that we wanted closed and de-referenced when the Employee was garbage collected, we could simply create the a destructor, which would be called by the GC when destroying the object.

In the following example there is an unassigned member variable created. This will represent the file handle that we will use, which the destructor will later clean up.

     using System;     using System.IO;     public class Employee     {       FileStream fStream; 

In the parameter-less constructor, add the following code to write to a file. We will not close or de-reference the fStream field at any stage during the object's lifetime.

     public Employee()     {       fStream = File.CreateText("C:\\temp.txt");       StreamWriter sw = new StreamWriter(fStream);       sw.Write("Default constructor called with no params");     } 

In order to invoke this new constructor we will reuse the code shown earlier as part of the Main() method, which creates three employee objects. Terminating the application will invoke the destructor code three times sequentially for each of the employee objects, based on the order in which the GC attempts to free memory for each of the objects.

The following code for the destructor is wrapped in a trycatch block as, if the object was created using one of the other constructors, fStream would be set as null and so we need to catch the exception:

     ~Employee()     {       try       {         fStream.Close();         fStream = null;       }       catch {}     } 

We have a modicum of control over whether we want this called for all three objects. For example, we don't really need to call the destructor for either of the objects that don't log anything to a file, as this will cause an exception that will terminate the execution of the destructor. In the parameterized constructor code, we can add the following line to ensure that the GC doesn't call the destructor for that particular object instance. However, in a production application the appropriate null reference checks should be applied within the destructor:

     GC.SuppressFinalize(this); 

There are occasions when a change is needed in this policy, so the GC provides an equivalent counter-method, which will allow the destructor to be called when collection occurs.

     GC.ReRegisterForFinalize(this); 

Although the GC knows when to collect all of the generations of objects on the managed heap, there may be occasions where the GC is not performing a necessary collection. An application may have few objects, but these objects contain many references to unmanaged resources. We can tell the GC to perform a collection to free more memory, which is otherwise not done. This is done by calling the Collect() method, although in most circumstances we would never need to do this as it generally has an adverse effect on the performance of an application. However there may be specific instances when not much memory is allocated on the managed heap where a few large objects that contain many references to unmanaged resources will not force a collection when memory needs to be reclaimed. In this instance we may wish to call GC.Collect() to possibly improve the performance of the application.

     GC.Collect() 

Using destructors is not recommended because it is costly to performance, although we will still need to clean up the resources used by the object before it is garbage collected. The GC builds a graph of all the objects that have yet to be de-referenced; that is, they have a reference count of greater than zero and so are still in use by the application code. All of the objects not graphed by the GC are destroyed as long as they don't have a destructor. If they do have a destructor they are moved to a special queue called the freachable (F-Reachable) queue, which is short for Finalization Reachable Queue, where a dedicated thread will manage all of the objects sequentially, calling their destructors before marking them as garbage. In this case, the memory of objects with destructors will not be reclaimed immediately as the object is not marked as garbage when the collection process begins. Therefore, a large number of destructors in an application will have an adverse effect on performance because a separate thread will be calling each destructor in sequence, and the memory will take longer to reclaim.

The preferred way to handle the disposal of objects is through the IDisposable interface. To use IDisposable, we have to implement the Dispose() method, which can act in the same way as the equivalent Finalize() or destructor method, except it doesn't interrupt the process of garbage collection or need to be queued and managed in the same way. In fact, the fundamental difference between the two methods of resource cleanup is that the Dispose() method can be called at any time by the calling code without incurring any performance cost to the application. It is encouraged that all resources are released on the Dispose() method and we should get used to implementing the IDisposable interface in classes that involve the release of unmanaged resources.

Dispose() is a convention and is generally used in objects that utilize many unmanaged resources such as calls to OLEDB libraries or file handles. Usually the Dispose() method is declared as virtual and is overridden in the derived class. In the derived class, the base class method is called to ensure that all resources derived from the base class are destroyed too.

The example following shortly defines a very simple inheritance hierarchy based on a shape. The important thing to identify here is the use of the Dispose() method. In the base class BasicShape, we implement the IDisposable interface and have to write an implementation for its one method, Dispose(). This will free the IO resources used by the class when called.

We don't create the BasicShape directly; instead we create a derived class called Square which will override both the GetCoords() and the Dispose() methods. With both of the derived class methods, GetCoords() and Dispose(), we should have a fuller class definition for the Square class including any code that is specific to the GetCoords() method in the derived class – possibly something using the example a and b fields (if x and y represent a single point then a and b represent the diagonal point forming the opposite corner of the square). The Dispose() method on the derived class can be used to clean-up any resources. The code snippet is contained within basic_shape.cs:

     using System;     using System.IO;     public abstract class BasicShape : IDisposable     {       protected int x;       protected int y;       private FileStream fStream;       public virtual void GetCoords()       {         //possibly open a file handle here         try         {           StreamReader sr =               new StreamReader((fStream = File.OpenRead("C:\\temp.txt")));           int x = Convert.ToInt32(sr.ReadLine());           int y = Convert.ToInt32(sr.ReadLine());         }         catch {}       }       public virtual void Dispose()       {         if(fStream!=null)       {         fStream.Close();         fStream = null;       }     }   }   public class Square : BasicShape   {     int a = 0;     int b = 0;     public override void GetCoords()     {       /** Do something associated with this class and then call the base           class method which will change inherited fields **/       base.GetCoords();     }     public override void Dispose()     {       //clean up any resources associated with this object       base.Dispose();     }   } 

The object must never throw an exception when de-referencing the resources on the derived class as this would prevent every Dispose() method in the class hierarchy from being called. We must write any cleanup code for the Square class before calling the base method of the BasicShape class and where possible anticipate exceptions so that the entire process doesn't fail. With very large inheritance hierarchies it is important that the Dispose() method is called for each base class and if the chain of calling Dispose() is interrupted then there will be some resources that are not released as a result. Below is the code for testing this in a Main() method:

     Square square = new Square();     square.GetCoords();     square.Dispose();     square = null; 

Many of the examples given on MSDN combine both the destructor and the IDisposable interface that will ensure that the Dispose() method gets called (although in actuality using the destructor is not necessary). If we do the same, insert a GC.SuppressFinalize(this); line at the end of the base class's Dispose() method, and set the destructor to call Dispose(). In this case, if Dispose() is never called, then it will be called by the destructor. If it is, then the destructor will never be called and so the memory can be reclaimed immediately upon garbage collection.

The Dispose() method can be called by many other different techniques – for example we could write our own managed class called GenericFile which could implement IDisposable. We could use the CreateFile API to obtain file handles in unmanaged code using P/Invoke and implement a Close() method in the class which called Dispose(). Dispose() would use the CloseHandle API to release the unmanaged resources. As this is part of the lifecycle of a file anybody using the class will explicitly call Close() thus calling Dispose() and releasing all the resources. Virtually all classes in .NET Framework Class Libraries operate like this and automate the invocation of the Dispose() method.




C# Class Design Handbook(c) Coding Effective Classes
C# Class Design Handbook: Coding Effective Classes
ISBN: 1590592573
EAN: 2147483647
Year: N/A
Pages: 90

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