Introduction.
This article is about understanding the working concept
of destructor in VB.NET. I know you all may be thinking
why a dedicated article on simple destructor phenomenon.
As you read this article you will understand how
different is VB.NETdestructor are when compared to C++
destructors.
In simple terms a destructor is a member that implements
the actions required to destruct an instance of a class.
The destructors enable the runtime system, to recover
the heap space, to terminate file I/O that is associated
with the removed class instance, or to perform both
operations. For better understanding purpose I will
compare C++ destructors with VB.NETdestructors.
Generally in C++ the destructor
is called when objects gets destroyed. And one can
explicitly call the destructors in C++. And also the
objects are destroyed in reverse order that they are
created in. So in C++ you have control over the
destructors.
One may be thinking how
VB.NETtreats the destructor. In VB.NETyou can never call
them, the reason is one cannot destroy an object. So who
has the control over the destructor (in VB.NET)? it's
the .Net frameworks Garbage Collector (GC).
Now few questions arise why GC
should control destructors why not us?
The answer is very simple GC can do better object
release than we can. If we do manual memory management
one has to take care both allocation and de-allocation
of memory. So there is always chance that one can forgot
de-allocation. And also manual memory management is time
consuming and complex process. So lets understand why
VB.NETforbids you from explicitly writing code for
destructors.
- If we are accessing
unmanaged code usually we forget to destroy an
object. This avoids the destructor call and memory
occupied by the object will never get released. Let
examine this case by taking example, Below figure
shows that Memory stacks in which application XYZ
loads an unmanaged code of 30 bytes. When
applications XYZ ends imagine it forgot to do
destroy an object in Unmanaged code so what happens
is Application XYZ memory gets deallocated back to
the heap but unmanaged code remains in memory. So
Memory gets wasted.
- If we are trying to
release the object while object is still doing some
process or i mean object is still active.
- If we are trying to
release the object that is already been released.
So lets see how GC will handle
above situations:
- If program ends GC
automatically reclaims all the memory occupied by
the objects in the program.
- GC keeps tracks of all the
objects and ensures that each object gets destroyed
once.
- GC ensures that objects,
which are being referenced, are not destroyed.
- GC destroys the objects
only when necessary. Some situations of necessity
are memory is exhausted or user explicitly calls
System.GC.Collect() method.
Understanding the complete
working of Garbage collector (GC) is a big topic. But I
will cover the some its details with respect to our
topic. GC is a .net framework thread, which runs when
needed or when other threads are in suspended mode. So
first GC creates the list of all the objects created in
the program by traversing the reference fields inside
the objects. This list helps the GC to know how many
objects it needs to keep track. Then it ensures that
there are no circular references inside this list. In
this list GC then checks for all the objects, which have
destructor, declared and place them in another list
called Finalization List.
So now GC creates two threads
one, which are reachable list and another unreachable,
or finalization List. Reachable objects are cleared one
by one from the list and memory occupied by these
objects are reclaimed back. The 2nd thread, which reads
the finalization lists and calls, the each object
finalized in separate object.
Lets see how VB.NETcompiler
understands the destructor code. Below is a small class
created in visual studio .Net, I have created a class
called class1 which has a constructor and a destructor.
Imports
System
Namespace
ConsoleApplication3
Class Class1
Public
Sub
New()
End
Sub 'New
Overloads
Overrides
Sub Finalize()
MyBase.Finalize()
End
Sub
'Finalize
'Entry point which delegates to C-style main Private
Function
Public
Overloads
Shared
Sub Main()
Main(System.Environment.GetCommandLineArgs())
End
Sub
Overloads Shared
Sub Main(ByVal
args() As
String)
Dim c
As New Class1
End
Sub 'Main
End
Class
'Class1
End
Namespace
'ConsoleApplication3
So after compiling the code open the assemblies in
ILDASM.EXE (Microsoft Diassembler Tool) tool and see the
IL code. You will see something-unusual code. In above
code the compiler automatically translates a destructor
into an override of the Object.Finalize() method. In
other words, the compiler translates the following
destructor:
Class Class1
Overrides Sub Finalize()
MyBase.Finalize()
End Sub 'Finalize
End Class 'Class1Into following:
In
Source Code Format : |
In IL
Code Format: |
Class Class1
Overrides Sub Finalize () '
Try
Finally
MyBase.Finalize()
End Try
End Sub 'Finalize
End Class 'Class1
|
.method
family hidebysig virtual instance void
Finalize() cil managed
{
// Code size 10 (0xa)
.maxstack 1
.try
{
IL_0000: leave.s IL_0009
} // end .try
finally
{
IL_0002: ldarg.0
IL_0003: call instance void [mscorlib]System.Object::Finalize()
IL_0008: endfinally
} // end handler
IL_0009: ret
} // end of method Class1::Finalize |
The compiler-generated Finalize
method contains the destructor body inside try block,
followed by a finally block that calls the base class
Finalize. This ensures that destructors always call its
base class destructor. So our conclusion from this is
Finalize is another name for destructors in C#.
Points to remember:
- Destructors are invoked
automatically, and cannot be invoked explicitly.
- Destructors cannot be
overloaded. Thus, a class can have, at most, one
destructor.
- Destructors are not
inherited. Thus, a class has no destructors other
than the one, which may be declared in it.
- Destructors cannot be used
with structs. They are only used with classes.
- An instance becomes
eligible for destruction when it is no longer
possible for any code to use the instance.
- Execution of the
destructor for the instance may occur at any time
after the instance becomes eligible for destruction.
- When an instance is
destructed, the destructors in its inheritance chain
are called, in order, from most derived to least
derived.