delphi – 为什么在迭代后自动释放TObjectList类型的列表?

前端之家收集整理的这篇文章主要介绍了delphi – 为什么在迭代后自动释放TObjectList类型的列表?前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。
我有一个关于 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;

如果我运行此代码,列表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实例.

原文链接:https://www.f2er.com/delphi/101981.html

猜你在找的Delphi相关文章