Tuesday, December 28, 2004

Delphi vs C#: Destructors

Delphi's implementation of destructors is very different from C#'s. This means that if you are translating C# code samples to Delphi, you have to be careful with how you convert C# destructors. A question from Ray in the delphi.rtl.dotnet newsgroup illustrates this problem:

"Subject: Garbage collector bug in Delphi.NET but not C#?
Hi there
In testing the Garbage collection in Delphi.NET I found the following.
Delphi.NET code

//******************** 
type
TDemo = class
public
class var Instances: integer;
constructor Create; virtual;
destructor Destroy; override;
end;
//futher down
procedure TWinForm.Button2_Click(sender: System.Object; e: System.EventArgs);
var
x: Integer;
a: TDemo;
begin
Text := 'Running';
for x := 1 to 100000 do begin
a := TDemo.Create;
a.Free;
end;
end;

procedure TWinForm.Timer1_Tick(sender: System.Object; e: System.EventArgs);
begin
Text := TDemo.Instances.ToString;
end;

procedure TWinForm.Button1_Click(sender: System.Object; e: System.EventArgs);
var
x: Integer;
a: TDemo;
begin
for x := 1 to 100000 do begin
a := TDemo.Create;
end;
end;

{ TDemo }

constructor TDemo.Create;
begin
inherited;
Inc(Instances);
end;

destructor TDemo.Destroy;
begin
Dec(Instances);
inherited;
end;
//********************

and the C#


//********************
  public class Demo
  {
    public static long Instances;
    public Demo()
    {
            Instances += 1;
    }
    ~Demo()
    {
       Instances--;
    }
  }
//Further down
  private void button1_Click(object sender, System.EventArgs e)
  {
    int Counter;
    Demo aDemo;
    for (Counter = 0; Counter < 100000; Counter++)
    {
      aDemo = new Demo();
    }
  }
  private void timer1_Tick(object sender, System.EventArgs e)
  {
    Text = Demo.Instances.ToString();
  }
//********************

Now the issue I have is with C# it will have 100 000 classes and then a few seconds later 37125 then click button and it's 2500 etc. Works like the help says...


BUT Delphi is "buggy". Button1 creates 100 000 and does NOT free even after multiple clicks, however button2 with the .Free frees instantly. So it works like Win32. Surely this is a bug (Not that I'm complaining I'd rather it free up memory when I say so).


Am I missing something?


Regards Roy"


This is not a bug - a Delphi destructor is not the same as a C# destructor.


A Delphi destructor implements the IDisposable pattern, while a C# destructor overrides the Finalize method of System.Object (a very rarely recommended practice). Look at the generated IL code from both compilers, and you will see the difference.


To make the comparison relevant, either change the Delphi code to override the Finalize method or change the C# code to implement the IDisposable interface. FWIW, the new C++/CLI standard implements C++ destructors as IDisposable implementations too. C# destructors are a mistake, IMO. Don't use them unless you know exactly what you are doing.


This triggered a follow-up question from Ray:



"I got the C# code from a MSCD.NET CD, and according to the demos / trainer material this is recommended? Besides VB.NET worked the same as C# so does "C# destructors are a mistake" hold true for VB.NET?


http://www.delphi.about.com/od/delphifornet/a/aa060104a.htm


"Every class in the .NET Framework inherits a method called Finalize. The bad news is that the GC calls the Finalize method (and therefore if overridden it should be protected) when the memory for the object is about to be freed. Since the method is called by the garbage collector, you have no control over when it is called.


The good news is that in Delphi for .NET, all objects implicitly implement IDisposable and redirect calls to Dispose to the Destroy destructor. This means that you do not need to implement Dispose yourself ... you can safely continue freeing resources (for objects/classes you develop) in the object's destructor "Destroy" method."


The part that threw me off was "you have no control over when it is called."


That's one of the reasons Finalize is generally not recommended. Another is that objects are kept alive much longer than needed.


Finalize is generally only needed for cleaning up unmanaged resources like file and window handles - and that should in 99% of the time be taken care of by small low-level wrapper classes defined in the .NET framework.


99% of the time you do not want to implement C# destructors in application-level code.


99% of the time(1) you do want to implement IDisposable in application-level code. Writing Delphi destructors makes this easy.


This artible by Brian Long is recommended reading:
http://bdn.borland.com/article/0,1410,29365,00.html


(1) The reason for this is that while finalizers are singular and automatically called by the GC (only the class holding references to unmanaged resource handles need finalizers), IDisposable requires a cascading implementation in wrapper objects. For instance, if a class A is implemented in terms of (by having a private reference to) a class B, and class B implements the IDisposable interface, the wrapping class A should also implement the IDisposable interface. This even includes cases where the implementation of A or B might change in the future to implement IDisposable.



Copyright © 2004-2007 by Hallvard Vassbotn