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
{
.maxstack 8
.try
{
IL_0000: ldstr "Instance destroyed."
IL_0005: call void [mscorlib]System.Console::WriteLine(string)
IL_000a: leave.s IL_0013
}
finally
{
IL_000c: ldarg.0
IL_000d: call instance void [mscorlib]System.Object::Finalize()
IL_0012: endfinally
}
IL_0013: ret
}
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.