- 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;
如果我运行此代码,列表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;
更新
我不得不重新考虑我接受的答案并得出以下结论:
在我看来,即使所描述的行为可能不是框架中的错误,Rudy的答案也是正确的.我认为Rudy通过指出框架应该按预期工作而提出了一个很好的论据.当我使用for-in循环时,我希望它是一个只读操作.之后清除列表并不是我预期会发生的.
另一方面,Fritzw和David Heffernan指出Spring4D框架的设计是基于接口的,因此应该以这种方式使用.只要记录了这种行为(也许Fritzw可以给我们提供文档的参考),我同意David的观点,即使我仍然认为框架的行为具有误导性,我对框架的使用也是不正确的.
我没有足够的经验来开发Delphi来评估所描述的行为是否真的是一个错误,因此撤销了我接受的答案,抱歉.
解决方法
TObjectList< T>旨在用作接口并具有引用计数.只要refcount达到0,实例就会被释放.
- procedure foo;
- var
- olist: TObjectList<TFoo>;
- o: TFoo;
- begin
- olist := TObjectList<TFoo>.Create();
olist的引用计数现在为0
- try
- olist.Add( TFoo.Create() );
- olist.Add( TFoo.Create() );
- for o in olist do
枚举器将olist的引用计数增加到1
- begin
- o.ToString();
- end;
枚举器超出范围并调用枚举器的析构函数,这会将olist的refcount减少为0,这意味着将释放olist实例.
- 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为0
- olisti := olist;
将olist引用分配给接口变量olisti将在内部调用olist上的_AddRef并将refcount增加到1.
- try
- olist.Add( TFoo.Create() );
- olist.Add( TFoo.Create() );
- for o in olist do
枚举器将olist的引用计数增加到2
- begin
- o.ToString();
- end;
枚举器超出范围并调用枚举器的析构函数,这会将olist的引用计数减少到1.
- finally
- //olist.Free(); -> this line is not required (?)
- end;
- end;
在过程结束时,接口变量olisti将设置为nil,它将在内部调用olist上的_Release并将refcount减少为0,这意味着将释放olist实例.
当我们将构造函数的引用直接分配给接口变量时,会发生同样的情况:
- procedure foo;
- var
- olist: IList<TFoo>;
- o: TFoo;
- begin
- olist := TObjectList<TFoo>.Create();
分配对接口变量olist的引用将在内部调用_AddRef并将refcount增加到1.
- olist.Add( TFoo.Create() );
- olist.Add( TFoo.Create() );
- for o in olist do
枚举器将olist的引用计数增加到2
- begin
- o.ToString();
- end;
枚举器超出范围并调用枚举器的析构函数,这会将olist的引用计数减少到1.
- end;
在过程结束时,接口变量olist将被设置为nil,这将在内部调用olist上的_Release并将refcount减少为0,这意味着将释放olist实例.