I have a question regarding the behaviour of the TObjectList class of the Spring4D framework. In my code I create a list of geometric figures such as square
, circle
, triange
, each defined as an individual class. To free the geometric figures automatically when the list gets destroyed I defined a list of type TObjectList like this:
procedure TForm1.FormCreate(Sender: TObject);
var
geometricFigures: TObjectList<TGeometricFigure>;
geometricFigure: TGeometricFigure;
begin
ReportMemoryLeaksOnShutdown := true;
geometricFigures := TObjectList<TGeometricFigure>.Create();
try
geometricFigures.Add(TCircle.Create(4,2));
geometricFigures.Add(TCircle.Create(0,4));
geometricFigures.Add(TRectangle.Create(3,10,4));
geometricFigures.Add(TSquare.Create(1,5));
geometricFigures.Add(TTriangle.Create(5,7,4));
geometricFigures.Add(TTriangle.Create(2,6,3));
for geometricFigure in geometricFigures do begin
geometricFigure.ToString();
end;
finally
//geometricFigures.Free(); -> this line is not required (?)
end;
end;
If I run this code the list geometricFigures
is freed automatically from memory even if I am not calling the method Free
on the list (notice commented out line in the finally block). I expected a different behaviour, I thought the list needs an explicit call to Free() because the local variable geometricFigures
is not using an interface type.
I further noticed that if the items of the list are not iterated in the for-in loop (I removed it from the code temporarily), the list is not freed automatically and i get a memory leak.
This leads me to the following question:
Why is the list of type TObjectList (geometricFigures
) freed automatically when its items are iterated but not if the for-in loop is removed from the code?
I followed the advice of Sebastian and debugged the destructor. The list items get destroyed by the following code:
{$REGION 'TList<T>.TEnumerator'}
constructor TList<T>.TEnumerator.Create(const list: TList<T>);
begin
inherited Create;
fList := list;
fList._AddRef;
fVersion := fList.fVersion;
end;
destructor TList<T>.TEnumerator.Destroy;
begin
fList._Release;
inherited Destroy; // items get destroyed here
end;
I had to reconsider my accepted answer and came to the following conclusion:
In my opinion Rudy's answer is correct even though the described behaviour might not be a bug in the framework. I think Rudy makes a good argument by pointing out that a framework should work as expected. When I am using a for-in loop I expect it to be a read-only operation. Clearing the list afterwards is not what I expected to happen.
On the other hand Fritzw and David Heffernan are pointing out that the design of the Spring4D framework is interface based and therefore should be used in that way. As long as this behaviour is documented (maybe Fritzw could give us a reference to the documentation) I agree with David that my usage of the framework is not correct even though I still think that the behaviour of the framework is missleading.
I am not experienced enough in developing with Delphi to evaluate if the described behaviour is actually a bug or not therefore revoked my accepted answer, sorry about that.
To do an iteration with for ... do
, the class must have a GetEnumerator
method. This obviously returns itself (i.e. the TObjectList<>
) as an IEnumerator<TGeometricFigure>
interface. After the iteration, the IEnumerator<>
is released, its reference count reaches 0, and the objectlist is freed.
This is a pattern you often see in, say, C#, but there, it doesn't have this effect, because the class instance is still referenced and the garbage collector will not jump in.
In Delphi however, this is a problem, as you can see. I guess the solution would be for the TObjectList<>
to have a separate (possibly nested) class or record that does the enumeration, and not to return Self
(as IEnumerator<>
). But this is up to the author of Spring4D. You could bring this problem to the attention of Stefan Glienke.
Your addendum shows that this is not exactly what happens. The TObjectList<>
(or to be more precise, its ancestor TList<>
) returns a separate enumerator, but that does an (IMO totally unnecessary, even if the list is used as interface from the start) _AddRef
/_Release
and the latter is the culprit.
I see multiple claims that in Spring4D, the class should not be used as class. Then such classes should not be exposed in the interface
section, but in the implementation
section of the unit instead. If such classes are exposed, the author should expect the user to use them. And if they are usable as class, then a for-in
loop should not free the container. One of these is a design problem: either the exposure as class, or the auto-freeing. So there is a bug, IMO.
To understand why the list is freed we need to understand what is going on behind the scenes.
TObjectList<T>
is designed to use as an interface and has reference counting. Whenever the refcount reaches 0 the instance will be freed.
procedure foo;
var
olist: TObjectList<TFoo>;
o: TFoo;
begin
olist := TObjectList<TFoo>.Create();
The refcount for olist
is now at 0
try
olist.Add( TFoo.Create() );
olist.Add( TFoo.Create() );
for o in olist do
The enumerator increase the refcount of olist
to 1
begin
o.ToString();
end;
The enumerator get out of scope and the destructor of the enumerator is called, which will decrease the refcount of olist
to 0, and that implies that the olist
instance is freed.
finally
//olist.Free(); -> this line is not required (?)
end;
end;
procedure foo;
var
olist: TObjectList<TFoo>;
olisti: IList<TFoo>;
o: TFoo;
begin
olist := TObjectList<TFoo>.Create();
olist
refcount is 0
olisti := olist;
Assigning the olist
reference to the interface variable olisti
will internally call _AddRef
on olist
and increase the refcount to 1.
try
olist.Add( TFoo.Create() );
olist.Add( TFoo.Create() );
for o in olist do
The enumerator increase the refcount of olist
to 2
begin
o.ToString();
end;
The enumerator get out of scope and the destructor of the enumerator is called, which will decrease the refcount of olist
to 1.
finally
//olist.Free(); -> this line is not required (?)
end;
end;
At the end of the procedure the interface variable olisti
will be set to nil
, which will internally call _Release
on olist
and decrease the refcount to 0 and that implies that the olist
instance is freed.
procedure foo;
var
olist: IList<TFoo>;
o: TFoo;
begin
olist := TObjectList<TFoo>.Create();
Assigning the reference to the interface variable olist
will internally call _AddRef
and increase the refcount to 1.
olist.Add( TFoo.Create() );
olist.Add( TFoo.Create() );
for o in olist do
The enumerator increase the refcount of olist
to 2
begin
o.ToString();
end;
The enumerator get out of scope and the destructor of the enumerator is called, which will decrease the refcount of olist
to 1.
end;
At the end of the procedure the interface variable olist
will be set to nil
, which will internally call _Release
on olist
and decrease the refcount to 0 and that implies that the olist
instance is freed.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With