Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Constructors, Destructors and MSIL

0.00/5 (No votes)
18 Sep 2001 1  
Using ILDASM to view custom destructors in C#

Introduction

I find C# a very interesting language and I find it handy to analyze the MSIL code generated by C# code.

To examine destructors and see how to write a custom destructor we will compile from the command line the following code:

class makecall
{
	void print()
	{
		System.Console.WriteLine("Executing print.");
		throw new System.NotImplementedException();
	}
	~makecall()
	{
		print();
	}
}
class test
{
	public static void Main()
	{
		makecall obj=new makecall();
		obj=null;
		System.GC.Collect();
		System.GC.WaitForPendingFinalizers();
	}
}

That is very similar to C++. 

A Few words about GC: If we comment out the lines:

System.GC.Collect();
System.GC.WaitForPendingFinalizers();

and reassign a new value to obj (insert the following straight after the end):

obj=new makecall();

during execution we will notice that both constructors are executed, but only at the end of the program will both destructors be called. The idea comes from a famous web site "How to write unmaintainable code" in the chapter about recycling variable names. Of course a "noisy" constructor is needed to see this. Something like:

public makecall ()
{
	System.Console.WriteLine("New instance created.");
}

Uncomment the GC lines and you will see the proper order of execution: first constructor and then the associated destructor.

Finally we can look at the assembler version of this code. Send the first version to the disassembler ILDASM.EXE and you will notice that the destructor has a catch-finally addition. Something like:

.method family hidebysig virtual instance void 
Finalize() cil managed
{
	// Code size 20 (0x14)

	.maxstack 8
	.try
	{
		IL_0000: ldstr "Instance destroyed."
		IL_0005: call void [mscorlib]System.Console::WriteLine(string)
		IL_000a: leave.s IL_0013
	} // end .try

	finally
	{
		IL_000c: ldarg.0
		IL_000d: call instance void [mscorlib]System.Object::Finalize()
		IL_0012: endfinally
	} // end handler

	IL_0013: ret
} // end of method Recycle::Finalize

ILASM does not trust our capabilities to write a custom destructor!

For more information about GC, Object.Finalize and Dispose, please check article "Gozer the Destructor" by Bobby Schmidt in the  MSDN .NET documentation. Of course you will have to compile the code and disassemble the exe file. Also there you can see information on System.IDisposable (another way to work with GC).

But my goal is not to review that article and repeat what is already written there. I want to show you how to insert a catch block and handle an exception according to your own needs. Why would we want to do that? If we are writing a component and we are going to do tests of that component and the application which uses it, we will need to log errors, warnings and other useful information. Standard quality assurance process. 

To implement a catch we will do the same as in the C# code. If we are dealing with an exception it will be at top of the stack. The code is as follows:

.assembly extern mscorlib{}
.assembly 'custom'{}
.class private auto ansi beforefieldinit makecall
extends [mscorlib]System.Object
{
    .method private hidebysig instance void 
    print() cil managed
    {
        ldstr "Executing print."
        call void [mscorlib]System.Console::WriteLine(string)
        newobj instance void [mscorlib]System.NotImplementedException::.ctor()
        throw
    }

    .method family hidebysig virtual instance void 
    Finalize() cil managed
    {
        .try
        {
            .try
            {
                ldarg.0
                call instance void makecall::print()
                leave.s ex

            }
            catch [mscorlib]System.Exception 
            {
                callvirt instance string [mscorlib]System.Exception::get_Message()
                call void [mscorlib]System.Console::WriteLine(string)
                leave.s ex
            }
        }
        finally
        {
            ldarg.0
            call instance void [mscorlib]System.Object::Finalize()
            endfinally
        }
        ex: ret
    }

    .method public hidebysig specialname rtspecialname 
    instance void .ctor() cil managed
    {
        ldarg.0
        call instance void [mscorlib]System.Object::.ctor()
        ret
    }
}

.method public static void Main() cil managed
{
    .entrypoint
    newobj instance void makecall::.ctor()
    pop
    call void [mscorlib]System.GC::Collect()
    call void [mscorlib]System.GC::WaitForPendingFinalizers()
    ret
}

Note the nested try block. As always if you are careful to not unbalance the stack MSIL is very friendly and helpful. You will probably prefer to log errors to a file. Tip: declare static void where you will do logging and pass to it error description in string. Try it, it's not difficult and could be useful in your future work with the .NET platform.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here