我有一个关于
Spring4D框架的TObjectList类的行为的问题.在我的代码中,我创建了一个几何图形列表,如square,circle,triange,每个都定义为一个单独的类.为了在列表被破坏时自动释放几何图形,我定义了一个类型为TObjectList的列表,如下所示:
为什么TObjectList(geometricFigures)类型的列表在迭代其项时会自动释放,但如果从代码中删除for-in循环则不会? @H_502_4@更新 @H_502_4@我按照塞巴斯蒂安的建议调试了析构函数.列表项被以下代码破坏:
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;@H_502_4@如果我运行此代码,列表geometricFigures会自动从内存中释放,即使我没有在列表中调用Free方法(注意finally块中注释掉的行).我期望一个不同的行为,我认为列表需要显式调用Free(),因为局部变量geometricFigures没有使用接口类型. @H_502_4@我进一步注意到,如果列表中的项目没有在for-in循环中迭代(我暂时将其从代码中删除),则列表不会自动释放,并且我会收到内存泄漏. @H_502_4@这引出了以下问题:
为什么TObjectList(geometricFigures)类型的列表在迭代其项时会自动释放,但如果从代码中删除for-in循环则不会? @H_502_4@更新 @H_502_4@我按照塞巴斯蒂安的建议调试了析构函数.列表项被以下代码破坏:
{$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;@H_502_4@更新 @H_502_4@我不得不重新考虑我接受的答案并得出以下结论: @H_502_4@在我看来,即使所描述的行为可能不是框架中的错误,Rudy的答案也是正确的.我认为Rudy通过指出框架应该按预期工作而提出了一个很好的论据.当我使用for-in循环时,我希望它是一个只读操作.之后清除列表并不是我预期会发生的. @H_502_4@另一方面,Fritzw和David Heffernan指出Spring4D框架的设计是基于接口的,因此应该以这种方式使用.只要记录了这种行为(也许Fritzw可以给我们提供文档的参考),我同意David的观点,即使我仍然认为框架的行为具有误导性,我对框架的使用也是不正确的. @H_502_4@我没有足够的经验来开发Delphi来评估所描述的行为是否真的是一个错误,因此撤销了我接受的答案,抱歉.
解决方法
要理解列表被释放的原因,我们需要了解幕后发生的事情.
@H_502_4@TObjectList< T>旨在用作接口并具有引用计数.只要refcount达到0,实例就会被释放.
procedure foo; var olist: TObjectList<TFoo>; o: TFoo; begin olist := TObjectList<TFoo>.Create();@H_502_4@olist的引用计数现在为0
try olist.Add( TFoo.Create() ); olist.Add( TFoo.Create() ); for o in olist do@H_502_4@枚举器将olist的引用计数增加到1
begin o.ToString(); end;@H_502_4@枚举器超出范围并调用枚举器的析构函数,这会将olist的refcount减少为0,这意味着将释放olist实例.
finally //olist.Free(); -> this line is not required (?) end; end;@H_502_4@使用接口变量有什么区别?
procedure foo; var olist: TObjectList<TFoo>; olisti: IList<TFoo>; o: TFoo; begin olist := TObjectList<TFoo>.Create();@H_502_4@olist refcount为0
olisti := olist;@H_502_4@将olist引用分配给接口变量olisti将在内部调用olist上的_AddRef并将refcount增加到1.
try olist.Add( TFoo.Create() ); olist.Add( TFoo.Create() ); for o in olist do@H_502_4@枚举器将olist的引用计数增加到2
begin o.ToString(); end;@H_502_4@枚举器超出范围并调用枚举器的析构函数,这会将olist的引用计数减少到1.
finally //olist.Free(); -> this line is not required (?) end; end;@H_502_4@在过程结束时,接口变量olisti将设置为nil,它将在内部调用olist上的_Release并将refcount减少为0,这意味着将释放olist实例. @H_502_4@当我们将构造函数的引用直接分配给接口变量时,会发生同样的情况:
procedure foo; var olist: IList<TFoo>; o: TFoo; begin olist := TObjectList<TFoo>.Create();@H_502_4@分配对接口变量olist的引用将在内部调用_AddRef并将refcount增加到1.
olist.Add( TFoo.Create() ); olist.Add( TFoo.Create() ); for o in olist do@H_502_4@枚举器将olist的引用计数增加到2
begin o.ToString(); end;@H_502_4@枚举器超出范围并调用枚举器的析构函数,这会将olist的引用计数减少到1.
end;@H_502_4@在过程结束时,接口变量olist将被设置为nil,这将在内部调用olist上的_Release并将refcount减少为0,这意味着将释放olist实例.