Introduction
This article is about understanding the working concept of destructor in C#. 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 C# destructor 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 purposes of a better
explanation I will compare C++ destructors with C# destructors.
Generally
in C++ the destructor is called when objects gets destroyed. And one can
explicitly call the destructors in C++. 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 C# treats the destructor. In C# you can never call them, the
reason is one cannot destroy an object explicitely. So who has the control over the
destructor (in C#)? it�s the .NET framework's Garbage Collector (GC).
Now a few questions arise, such as why the GC should control destructors and 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 a time consuming and complex process. So lets
understand why C# forbids 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 forgets to destroy an
object in Unmanaged code. What happens is Application XYZ memory gets
deallocated back to the heap but the unmanaged code remains in memory. So the
memory
gets wasted.

-
If we
are trying to release the object while object is still doing some process or 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 C# compiler 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.
using System;
namespace ConsoleApplication3
{
class Class1
{
public Class1()
{}
~Class1()
{}
static void Main(string[] args)
{
Class1 c= new Class1();
}
}
}
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
{
~Class1(){}
}
Into the following:
In
Source Code Format:
|
In
IL Code Format:
|
class Class1
{
Protected override void Finalize()
{
try{..}
finally { base.Finalize();}
}
}
|
.method family hidebysig virtual instance void
Finalize() cil managed
{
.maxstack 1
.try
{
IL_0000: leave.s IL_0009
}
finally
{
IL_0002: ldarg.0
IL_0003: call instance
void [mscorlib]System.Object::Finalize()
IL_0008: endfinally
}
IL_0009: ret
}
|
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.
Further
reading
- .NET
More information on .Net technologies