Click here to Skip to main content
Click here to Skip to main content

9 Rules about Constructors, Destructors, and Finalizers

, 27 May 2010
Rate this:
Please Sign up or sign in to vote.
in C#, C++/CLI, and ISO/ANSI C++

Overview

First, this writing concentrates of and compares between three programming languages, C#, C++/CLI, and ISO/ANSI C++. It discusses 9 rules that every developer should keep in mind while working with constructors, destructors, and finalizers and class hierarchies:

  • Rule #1: Contrsuctors are called in descending order
  • Rule #2: In C# lexicology, a destructor and a finalizer refer to the same thing
  • Rule #3: Destructors are called in ascending order
  • Rule #4: Finalizers are a feature of GC-managed objects only
  • Rule #5: You cannot determine when finalizers would be called
  • Rule #6: C++/CLI differs between destructors and finalizers
  • Rule #7: In C++/CLI and classic C++, you can determine when destructors are called
  • Rule #8: In C++/CLI, destructors and finalizers are not called together
  • Rule #9: Beware of virtual functions in constructors

Rule #1: Constructors are called in descending order

Rule #1: Constructors are called in descending order; starting from the root class and stepping down through the tree to reach the last leaf object that you need to instantiate. Applies to C#, C++/CLI, and ANSI C++.

Let’s consider a simple class hierarchy like this:

    class BaseClass
    {
        public BaseClass()
        {
            Console.WriteLine("ctor of BaseClass");
        }
    }

    class DerivedClass : BaseClass
    {
        public DerivedClass()
        {
            Console.WriteLine("ctor of DerivedClass");
        }
    }

    class ChildClass : DerivedClass
    {
        public ChildClass()
        {
            Console.WriteLine("ctor of ChildClass");
        }
    }

ChildClass inherits from DerivedClass, and DerivedClass, in turn, inherits from BaseClass.

When we create a new instance of ChildClass using a simple line like this:

    static void Main()
    {
        ChildClass cls = new ChildClass();
    }

the code outputs the following results:

ctor of BaseClass
ctor of DerivedClass
ctor of ChildClass

Rule #2: In C# lexicology, a destructor and a finalizer refer to the same thing

Rule #2: In C# lexicology, a destructor and a finalizer refer to the same thing; the function called before the object is fully-removed from the memory (i.e. GC-collected). Applies to C# only.

Let’s consider the same class hierarchy but with destructors:

    class BaseClass
    {
        public ~BaseClass()
        {
            Console.WriteLine("dtor of BaseClass");
        }
    }

    class DerivedClass : BaseClass
    {
        public ~DerivedClass()
        {
            Console.WriteLine("dtor of DerivedClass");
        }
    }

    class ChildClass : DerivedClass
    {
        public ~ChildClass()
        {
            Console.WriteLine("dtor of ChildClass");
        }
    }

When you define a class destructor with that C++-alike syntax (preceding the function name with a ~) the compiler actually replaces your destructor with an override of the virtual Object.Finalize() function. That is, before the object is removed (i.e. GC-collected) from the memory, the finalizer (i.e. destructor) is called first. This finalizer first executes your code. After that it calls the finalizer of the base type of your object. If we could decompile our assembly, we would see that our destructor in the ChildClass (so other classes) has been replaced with this function:

        protected virtual void Finalize()
        {
            try
            {
                Console.WriteLine("dtor of ChildClass");
            }
            finally
            {
                base.Finalize();
            }
        }

Rule #3: Destructors are called in ascending order

Rule #3: Destructors are called in ascending order, starting from the leaf object that you need to instantiate and moving up through the tree to reach the very first base class of your object. In reverse of constructors calling order. Applies to C#, C++/CLI, and ANSI C++.

Now, instantiate your class:

    static void Main()
    {
        ChildClass cls = new ChildClass();

        // 'cls' is removed from memory here
    }

the code should outputs the following results:

dtor of ChildClass
dtor of DerivedClass
dtor of BaseClass

Rule #4: Finalizers are a feature of GC-managed objects

Rule #4: Finalizers are a feature of GC managed objects (i.e. managed classes). That’s because the finalizer is called only when the GC removes the object from the memory (i.e. frees memory associated with).

Now, try to create a simple structure with a destructor:

    struct MyStruct
    {
        ~MyStruct()
        {
            Console.WriteLine("dtor of MyStruct");
        }
    }

The code won’t compile. That’s because that GC doesn’t handle structures.

Rule #5: You can’t determine when finalizers would be called

That’s because you don’t know when the next garbage collection would occur, even if you performed a manual garbage collection (using System.GC.Collect() function) you won’t know exactly when memory would be released. In addition, GC always delay releasing of finalizable object, it puts them in a special GC queue called freachable (pronounced ef-reachable, F stands for Finalize) queue. Applies to C# and C++/CLI (.NET.)

Rule #6: C++/CLI differs between destructors and finalizers

Rule #6: C++/CLI differs between destructors and finalizers. That is, finalizers are called by GC, and destructors are called when you manually delete the object.

Let’s consider the same example but in C++/CLI:

ref class BaseClass
{
public:
	BaseClass()
	{
		Console::WriteLine("ctor of BaseClass");
	}

	~BaseClass()
	{
		Console::WriteLine("dtor of BaseClass");
		GC::ReRegisterForFinalize(this);
	}
};

ref class DerivedClass : BaseClass
{
public:
	DerivedClass()
	{
		Console::WriteLine("ctor of DerivedClass");
	}

	~DerivedClass()
	{
		Console::WriteLine("dtor of DerivedClass");
		GC::ReRegisterForFinalize(this);
	}
};

ref class ChildClass : DerivedClass
{
public:
	ChildClass()
	{
		Console::WriteLine("ctor of ChildClass");
	}

	~ChildClass()
	{
		Console::WriteLine("dtor of ChildClass");
		GC::ReRegisterForFinalize(this);
	}
};

When we run the code:

int main()
{
	ChildClass^ cls = gcnew ChildClass();
}

it outputs the following results:

ctor of BaseClass
ctor of DerivedClass
ctor of ChildClass

The destructors are not called. Why? Unlike C#, in C++/CLI there is a big difference between destructors and finalizers. As you know, the finalizer is called when the GC removes the object from the memory. Destructors, on the other hand, are called when you destroy the object yourself (e.g. use the delete keyword.)

Now, try to change the test code to the following:

int main()
{
	ChildClass^ cls = gcnew ChildClass();
	delete cls;
}

Run the code. Now, destructors are called.

Next, let’s add finalizers to our objects. The code should be like the following:

ref class BaseClass
{
public:
	BaseClass()
	{
		Console::WriteLine("ctor of BaseClass");
	}

	~BaseClass()
	{
		Console::WriteLine("dtor of BaseClass");
		GC::ReRegisterForFinalize(this);
	}
	!BaseClass()
	{
		Console::WriteLine("finz of BaseClass");
	}
};

ref class DerivedClass : BaseClass
{
public:
	DerivedClass()
	{
		Console::WriteLine("ctor of DerivedClass");
	}

	~DerivedClass()
	{
		Console::WriteLine("dtor of DerivedClass");
		GC::ReRegisterForFinalize(this);
	}
	!DerivedClass()
	{
		Console::WriteLine("finz of DerivedClass");
	}
};

ref class ChildClass : DerivedClass
{
public:
	ChildClass()
	{
		Console::WriteLine("ctor of ChildClass");
	}

	~ChildClass()
	{
		Console::WriteLine("dtor of ChildClass");
		GC::ReRegisterForFinalize(this);
	}
	!ChildClass()
	{
		Console::WriteLine("finz of ChildClass");

	}
};

As you see, the syntax of constructors, destructors, and finalizers are very similar.

Now, let’s try the code:

int main()
{
	ChildClass^ cls = gcnew ChildClass();
}

GC would call finalizers and the code would outputs the following:

ctor of BaseClass
ctor of DerivedClass
ctor of ChildClass
finz of ChildClass
finz of DerivedClass
finz of BaseClass

Rule #7: In C++/CLI and C++, you can determine when destructors are called

Now, try to destroy the object yourself:

int main()
{
	ChildClass^ cls = gcnew ChildClass();
	delete cls;
}

The delete statement calls object destructors and removes the object from memory.

Or else, declare the object with stack-semantics:

int main()
{
	ChildClass cls;
}

Now, destructors are called when the scope of the object ends.

Rule #8: In C++/CLI, destructors and finalizers are not called together

Rule #8: In C++/CLI, destructors and finalizers are not called together. Only destructors or finalizers are called. If you manually delete the object or you declare it with stack-semantics, destructors are called. If you leaved the object for GC to handle, finalizers are called.

Now try to run the code. The code should outputs the following results:

ctor of BaseClass
ctor of DerivedClass
ctor of ChildClass
dtor of ChildClass
dtor of DerivedClass
dtor of BaseClass

Rule #9: Beware of virtual functions in constructors

Rule #9: Beware of virtual (overridable) functions in constructors. In .NET (C# and C++/CLI,) the overload of the most derived object (the object to be instantiated) is called. In traditional C++ (ISO/ANSI C++,) the overload of the current object constructed is called.

Let’s update our C# example:

class BaseClass
{
    public BaseClass()
    {
        Foo();
    }

    public virtual void Foo()
    {
        Console.WriteLine("Foo() of BaseClass");
    }
}

class DerivedClass : BaseClass
{
    public DerivedClass()
    {
    }

    public override void Foo()
    {
        Console.WriteLine("Foo() of DerivedClass");
    }
}

class ChildClass : DerivedClass
{
    public ChildClass()
    {
    }

    public override void Foo()
    {
        Console.WriteLine("Foo() of ChildClass");
    }
}

When you execute the code:

    static void Main()
    {
        ChildClass cls = new ChildClass();
    }

you would get the following results:

Foo() of ChildClass

The same code in C++/CLI:

ref class BaseClass
{
public:
	BaseClass()
	{
		Foo();
	}

	virtual void Foo()
	{
		Console::WriteLine("Foo() of BaseClass");
	}
};

ref class DerivedClass : BaseClass
{
public:
	DerivedClass()
	{
	}

	virtual void Foo() override
	{
		Console::WriteLine("Foo() of DerivedClass");
	}
};

ref class ChildClass : DerivedClass
{
public:
	ChildClass()
	{
	}

	virtual void Foo() override
	{
		Console::WriteLine("Foo() of ChildClass");
	}
};

The code outputs the same results.

But what if you need to call the virtual function of the BaseClass? Just change the code to the following:

ref class BaseClass
{
public:
	BaseClass()
	{
		BaseClass::Foo();
	}

	virtual void Foo()
	{
		Console::WriteLine("Foo() of BaseClass");
	}
};

Now, the code outputs:

Foo() of BaseClass

Let’s consider the same example but in classic ISO/ANSI C++:

class CBaseClass
{
public:
	CBaseClass()
	{
		Foo();
	}
	virtual void Foo()
	{
		cout << "Foo() of CBaseClass" << endl;
	}
};

class CDerivedClass : CBaseClass
{
public:
	CDerivedClass()
	{
	}

	virtual void Foo() override
	{
		cout << "Foo() of CDerivedClass" << endl;
	}
};

class CChildClass : CDerivedClass
{
public:
	CChildClass()
	{
	}

	virtual void Foo() override
	{
		cout << "Foo() of CChildClass" << endl;
	}
};

Now, run the code. It should outputs:

Foo() of BaseClass

In classic C++, the overload of the function of the class being constructed is called unlike C# and C++/CLI (.NET in general.)


Filed under: C#, C++/CLI, The Language Tagged: .NET, C, CodeProject, CPP, CSharp, MCPP

License

This article, along with any associated source code and files, is licensed under The Creative Commons Attribution 3.0 Unported License

Share

About the Author

Mohammad Elsheimy
Technical Writer Just Like a Magic
Egypt Egypt
Independent technical writer from Egypt born in 1991.
 
I am a student of The Holy Quranic Sciences Institute. There, we study the Holy Quranic sciences and Islamic legislation.
 
I currently run two sites:
Follow on   Twitter

Comments and Discussions

 
GeneralStatic Contructor in C# PinmemberArindam Sinha31-May-10 23:07 
GeneralRe: Static Contructor in C# PinmemberMohammad Elsheimy1-Jun-10 10:45 
GeneralPlease use a spell checker Pinmembertonyt27-May-10 11:45 
GeneralRe: Please use a spell checker PinmemberMohammad Elsheimy28-May-10 6:30 
GeneralRe: Please use a spell checker Pinmemberwim ton31-May-10 10:18 
GeneralRe: Please use a spell checker PinmemberMohammad Elsheimy31-May-10 11:19 
GeneralMC++ and C++/CLI are not the same thing PinmemberMihai Maerean25-May-10 17:54 
GeneralRe: MC++ and C++/CLI are not the same thing PinmemberMohammad Elsheimy27-May-10 5:34 
GeneralConstructor parameters Pinmembersupercat929-Apr-10 7:40 
GeneralRe: Constructor parameters PinmemberMohammad Elsheimy30-Apr-10 2:31 
GeneralRe: Constructor parameters Pinmembersupercat930-Apr-10 5:26 
GeneralRe: Constructor parameters PinmemberMohammad Elsheimy30-Apr-10 8:23 
GeneralRe: Constructor parameters Pinmembersupercat930-Apr-10 11:12 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web04 | 2.8.140814.1 | Last Updated 27 May 2010
Article Copyright 2010 by Mohammad Elsheimy
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid