我有一个关于Spring4D框架的TObjectList类的行为的问题.在我的代码创建的几何图形,诸如列表square,circle,triange,各自定义一个单独的类.为了在列表被破坏时自动释放几何图形,我定义了一个类型为TObjectList的列表,如下所示:
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;
Run Code Online (Sandbox Code Playgroud)
如果我运行此代码,geometricFigures即使我没有Free在列表中调用方法,也会自动从内存中释放列表(注意在finally块中注释掉了行).我期望一个不同的行为,我认为该列表需要显式调用Free(),因为局部变量geometricFigures不使用接口类型.
我进一步注意到,如果列表中的项目没有在for-in循环中迭代(我暂时将其从代码中删除),则列表不会自动释放,并且我会收到内存泄漏.
这引出了以下问题:为什么TObjectList(geometricFigures)类型的列表在迭代其项时会自动释放,但如果从代码中删除for-in循环则不会?
我按照塞巴斯蒂安的建议调试了析构函数.列表项被以下代码破坏:
{$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;
Run Code Online (Sandbox Code Playgroud)
我不得不重新考虑我接受的答案并得出以下结论:
在我看来,即使所描述的行为可能不是框架中的错误,Rudy的答案也是正确的.我认为Rudy通过指出框架应该按预期工作而提出了一个很好的论据.当我使用for-in循环时,我希望它是一个只读操作.之后清除列表并不是我预期会发生的事情.
另一方面,Fritzw和David Heffernan指出Spring4D框架的设计是基于接口的,因此应该以这种方式使用.只要记录了这种行为(也许Fritzw可以给我们提供文档的参考),我同意David的看法,即使我仍然认为框架的行为具有误导性,我对框架的使用也是不正确的.
我没有足够的经验来开发使用Delphi来评估所描述的行为是否真的是一个错误,因此撤销了我接受的答案,抱歉.
要进行迭代for ... do,该类必须有一个GetEnumerator方法.这显然将自身(即TObjectList<>)作为IEnumerator<TGeometricFigure> 接口返回.迭代后,IEnumerator<>释放,其引用计数达到0,并释放对象列表.
这是您经常在C#中看到的模式,但在那里,它没有这种效果,因为类实例仍然被引用而垃圾收集器不会跳转.
但是在Delphi中,正如您所看到的,这是一个问题.我想解决方案是TObjectList<>让一个单独的(可能是嵌套的)类或记录进行枚举,而不是返回Self(as IEnumerator<>).但这取决于Spring4D的作者.你可以把这个问题引起Stefan Glienke的注意.
你的附录表明,这并不是真正发生的事情.的TObjectList<>(或更精确地说,它的祖先TList<>)返回一个单独的枚举器,但也不一(IMO完全不必要,即使该列表被用作从开始接口)_AddRef/ _Release后者是罪魁祸首.
我看到多个声称在Spring4D中,该类不应该用作类.那么这些类不应该在该interface部分中公开,而是在implementation单元的部分中公开.如果暴露了这样的类,作者应该期望用户使用它们.如果它们可用作类,则for-in循环不应释放容器.其中一个是设计问题:暴露为类或自动释放.所以有一个错误,IMO.
要了解为什么释放列表,我们需要了解幕后发生的事情。
TObjectList<T>旨在用作接口并具有引用计数。每当引用计数达到 0 时,实例将被释放。
procedure foo;
var
olist: TObjectList<TFoo>;
o: TFoo;
begin
olist := TObjectList<TFoo>.Create();
Run Code Online (Sandbox Code Playgroud)
的 refcountolist现在为 0
try
olist.Add( TFoo.Create() );
olist.Add( TFoo.Create() );
for o in olist do
Run Code Online (Sandbox Code Playgroud)
枚举器将 refcountolist增加到 1
begin
o.ToString();
end;
Run Code Online (Sandbox Code Playgroud)
枚举器超出范围并调用该枚举器的析构函数,这会将 refcount 减少olist到 0,这意味着该olist实例已被释放。
finally
//olist.Free(); -> this line is not required (?)
end;
end;
Run Code Online (Sandbox Code Playgroud)
procedure foo;
var
olist: TObjectList<TFoo>;
olisti: IList<TFoo>;
o: TFoo;
begin
olist := TObjectList<TFoo>.Create();
Run Code Online (Sandbox Code Playgroud)
olist 引用计数为 0
olisti := olist;
Run Code Online (Sandbox Code Playgroud)
分配olist参考接口变量olisti将内部调用_AddRef上olist,并增加引用计数为1。
try
olist.Add( TFoo.Create() );
olist.Add( TFoo.Create() );
for o in olist do
Run Code Online (Sandbox Code Playgroud)
枚举器将 refcountolist增加到 2
begin
o.ToString();
end;
Run Code Online (Sandbox Code Playgroud)
枚举器超出范围并调用枚举器的析构函数,这会将 refcount 减少olist到 1。
finally
//olist.Free(); -> this line is not required (?)
end;
end;
Run Code Online (Sandbox Code Playgroud)
在界面处的可变程序结束olisti将被设置为nil,其将内部调用_Release上olist并降低引用计数为0,并且意味着olist实例被释放。
procedure foo;
var
olist: IList<TFoo>;
o: TFoo;
begin
olist := TObjectList<TFoo>.Create();
Run Code Online (Sandbox Code Playgroud)
将引用分配给接口变量olist将在内部调用_AddRef并将引用计数增加到 1。
olist.Add( TFoo.Create() );
olist.Add( TFoo.Create() );
for o in olist do
Run Code Online (Sandbox Code Playgroud)
枚举器将 refcountolist增加到 2
begin
o.ToString();
end;
Run Code Online (Sandbox Code Playgroud)
枚举器超出范围并调用枚举器的析构函数,这会将 refcount 减少olist到 1。
end;
Run Code Online (Sandbox Code Playgroud)
在界面处的可变程序结束olist将被设置为nil,其将内部调用_Release上olist并降低引用计数为0,并且意味着olist实例被释放。