Demystify Garbage Collection in C#: Part 3

Welcome to the Garbage Collection article series. If this is the first article you are reading in the Garbage Collection series then I suggest you visit my previous two articles on the same topic. Here are the links.

Demystify Garbage Collection in C#: Part-1

Demystify Garbage Collection in C#: Part-2

Let's start today's discussion. Today we will clarify one fundamental idea of Garbage Collection. The ideas is that a destructor is nothing but a finally block in C# and the finally block is always used to clean up resources.

If you are familiar with C++ development then I hope you are familiar with destructors in C++ and probably you have used them to clean up resources created by the constructor, basically.

With a small example we will see how a destructor is like a finally block in IL code. Have a look at the following example. In the MyClass class we have defined one constructor and one destructor.
 

using System;

using System.Collections;

using System.Globalization;

using System.Data.SqlClient;

using System.Data;

 

namespace Test1

{

    class MyClass

    {

        public MyClass()

        {

        }

        ~MyClass()

        {

 

        }

    }

    class Program

    {

        static void Main(string[] args)

        {

            Console.ReadLine();

        }

    }

}

I have compiled it and here is the screen of IL code. In the screen we are seeing one finally block.

GarbageCollection1.jpg

In the upper portion we are seeing the component of the assembly and in the lower portion the body of the destructor.

Now in the output we are seeing one finally block, although we did not define a finally block anywhere in the program.

What happens when we use a using block?

Again this is another fundamental idea. If you are a little experience in C# then you are probably aware of using blocks. And hopefully you know that a using block is used to clean up resources properly. In the following example we will see how a using block converts into a try-finally block internally.

Here we have created one connection object within the using block.
 

using System;

using System.Collections;

using System.Globalization;

using System.Data.SqlClient;

using System.Data;

 

namespace Test1

{

    class Program

    {

        static void Main(string[] args)

        {

            using (SqlConnection con = new SqlConnection())

            {

   

            }

            Console.ReadLine();

        }

    }

}


Here is a sample output screen.

GarbageCollection2.jpg

In the output we are seeing a using has been converted to a try-finally block. And when all functions with the object have finished the finally block will execute. And it will free resources associated with the object.

Now, in the next example we will discuss a very interesting fact about destructors and Garbage Collection.

How a destructor creates a problem in Garbage Collection

I hope you will not agree with me. Because you have the concept of "write all clean up code within a destructor and when the object goes out of scope the destructor will run and clean up resources from memory".
In the following example we will learn how that concept is wrong for C# .NET Garbage Collection.
At first we will read the story behind the technique and then we will go for a practical demonstration. Actually when we introduce the destructor or finalize method the job of garbage collector became more complex. How? Please go through the following few lines very carefully.

A new object having a Finalize method is allocated on the heap and a pointer to the object is stored in an internal data structure called the Finalization queue. When an object is not reachable the garbage collector considers the object as garbage. Then the garbage collector scans the Finalization queue to search for the object's pointer (the garbage object). When a pointer is found, the pointer is removed from the finalization queue and appended to another data structure called the Freachable queue.

The greatest tragedy happens next. The next time the garbage collector never considers the object as garbage.

So, that's why it is always recommended to not to use a destructor unless required.

In the following we will arrange a demonstration to prove this concept. At first we will define one class with a destructor and will create a huge number of objects of the same class and then we will profile this application using the CLR profiler. Here is our first program.
 

using System;

using System.Collections.Generic;

using System.ComponentModel;

using System.Data;

using System.Linq;

using System.Windows.Forms;

using System.Data.SqlClient;

 

namespace WindowsForm

{

    public partial class Form1 : Form

    {

        public Form1()

        {

            InitializeComponent();

        }

        private void Form1_Load(object sender, EventArgs e)

        {

        }

        private void button1_Click_1(object sender, EventArgs e)

        {

            Garbage g =null;

            for (int i = 0; i < 1000000; i++)

            {

                g = new Garbage();

            }

           

            MessageBox.Show("Form Completed..");

        }

       

    }

    public class Garbage

    {

        public String name = "";

        public Garbage()

        {

        }

        ~Garbage()

        {

        }

 

    }

}


Here is the output of the CLR profiler to detect generation of objects.

GarbageCollection3.jpg

Very clearly we are seeing that most of the objects are created in generation 1. In other words objects are occupying memory for most of the time. We will consider that to be a bad programming practice.

Now we will remove the destructor and will profile the same application again. Here is the sample example.
 

using System;

using System.Collections.Generic;

using System.ComponentModel;

using System.Data;

using System.Linq;

using System.Windows.Forms;

using System.Data.SqlClient;

 

namespace WindowsForm

{

    public partial class Form1 : Form

    {

        public Form1()

        {

            InitializeComponent();

        }

        private void Form1_Load(object sender, EventArgs e)

        {

        }

        private void button1_Click_1(object sender, EventArgs e)

        {

            Garbage g =null;

            for (int i = 0; i < 1000000; i++)

            {

                g = new Garbage();

            }

           

            MessageBox.Show("Form Completed..");

        }

       

    }

    public class Garbage

    {

        public String name = "";

        public Garbage()

        {

        }

    }

}


The output screen from the CLR profiler.

GarbageCollection4.jpg

Now we can see that most of the objects have been created in generation 0, in other words the objects are not staying in memory for a long time. This is a good sign of the best practice.

Up Next
    Ebook Download
    View all
    Learn
    View all