C# front end destructors vs generated CLR code
For those with a C++ background, C# destructors are not the same as C++ destructors. A C++ destructor in the language front end, that is the C++ syntax, remains the same in the backend execution engine (native code); whereas a C# destructor in the language front end does not remain the same in the backend execution engine (CLR). A C# destructor is generated as a CLR finalizer because finalizer is what CLR knows, the CLR does not know nothing about destructors. The execution of C# destructors is non-deterministic whereas the execution of C++ destructors is deterministic. That is, in C++ when an object is de-allocated its destructor is immediately executed, whereas when a C#/CLR object is no longer referenced the following apply: "The CLR Garbage Collector makes the promise to at least intend to execute the object finalizer before the executing process ends". This has been an area of much confusion in the field, and has lead to serious design flaws in business applications which root cause is that very confusion.
The C# language front end for destructors is similar to C++ destructors syntax but because of the difference in generated code behavior there are implications that anyone using C# destructors should be aware of; especially on the context of garbage collection. Let’s see what I mean. Consider the following C# code:
class A
{
~A() {System.Console.WriteLine("A.~A()");}
}
The C# compiler generates the following C# Finalize method for the ~A destructor:
protected override void Finalize()
{
try
{
System.Console.WriteLine("A.~A()");
}
finally
{
base.Finalize();
}
}
The essence of the matter is _when_ this Finalize method is going to be executed by the CLR Garbage Collector. The answer is: You don't know (that is the nature of non-deterministic). Some time in the future, the next time the Garbage Collector runs and happens the object is actually selected to be finalized. You have this guarantee: anytime before the executing process ends; in the context of long running processes like ASP.NET worker processes which can be up and running from minutes to months, the Finalize method would not be executed until that time. So for example:
~PressureClass()
{
GC.RemoveMemoryPressure(this.pressure);
}
or
~UnitOfWork()
{
this.connection.Close();
}
In the context of a long-running process, When that memory pressure is going to be removed from the garbage collector? or When that SQL connection is going to be released? Answer: at an unknown time.
My current conclusion is: do not use C# destructors for any time-sensible operation; be aware of C# destructors are CLR finalizers as of CLR 1.0 and 1.1 when taking design decisions targeting those execution engines.
Some references:
Garbage Collection—Part 1: Automatic Memory Management in the Microsoft .NET Framework
Garbage Collection—Part 2: Automatic Memory Management in the Microsoft .NET Framework
Comments
- Anonymous
April 08, 2005
>> This has been an area of much confusion
>> in the field, and has lead to serious
>> design flaws in business applications
>> which root cause is that very confusion.
Perhaps this indicates a design flaw in C# destructors. If deterministic finalization is an intutitive expectation of developers and designers, why should it be necessary for them to overcome their intuition and work around limitations of the CLR?
It's not a good excuse to say that the CLR doesn't support it. Computers are powerful. Language designers are smart. There is no justifiable reason for a brand new language like C# to burden the programmer with another set of bookkeeping chores that should be handled by powerful computers and smart language designers. - Anonymous
April 08, 2005
As a response to that, see:
http://blogs.msdn.com/marcod/archive/2005/04/08/CSMindSet.aspx - Anonymous
May 14, 2005
RePost:
http://www.yeyan.cn/SoftwareEngineering/CsharpCLR.aspx