为什么Generics.Collections.TObjectList.List不安全?

时间:2014-03-28 13:07:57

标签: delphi generics

TList中的{p> TOjectListGenerics.Collections具有.List属性,这是一个枚举器。

例如:

oList := TObjectList<TItem>.Create;
// Add items to oList
for Item in oList.List do begin
  // Do something with Item
end;

这很整洁,但结果却很严重。 .List只需阅读FListTListTObjectList上的私人声明),这只是arrayofT

由于动态数组的大小加倍,只要项目的大小超出其大小,就意味着它有空间用于未使用的项目。

如果您添加了3个TItem,则实际的FList长度为4个项目,第四个(和最后一个)项目为nil

因此,使用TObjectList的{​​{1}}是不安全的,因为如果您的.List没有TObjectList值,那么它可能会导致访问冲突2(例如1,2,4,8,16等)。

以下代码可能会导致访问冲突:

.Count

当然,安全解决方案是使用for Item in oList.List do begin Writeln(Item.ClassName); end;

的简单迭代
.Count

这不像那样和枚举器一样。 (当然,您也可以检查for I := 0 to oList.Count - 1 do begin Item := oList.Items[I]; Writeln(Item.ClassName); end; 是否为Item。)

我的问题是:

  • 为什么nil不是实际的枚举符?
  • .List / TList 实际的枚举符吗?

以下是TObjectList的示例(其中TForm只添加一行,btn1mmo1)。

TMemo

现在,使用procedure TForm2.btn1Click(Sender: TObject); var Line: string; begin Line := 'Line'; mmo1.Lines.Add(Line); fList.Add(Line); mmo1.Lines.Add(Format('Count: %d; Actual length: %d', [fList.Count, Length(fList.List)])); for Line in fList.List do begin mmo1.Lines.Add(Format('Found: "%s"', [Line])); end; end; 不会引发访问冲突。但是当我点击3次后,我得到以下内容:

string

2 个答案:

答案 0 :(得分:16)

  TList<T>中的{p> TOjectList<T>Generics.Collections具有List属性,这是一个枚举器。

不,不是这样。 List属性是动态数组。动态数组内置了对枚举的支持。

  

由于动态数组的大小加倍,只要项目的大小超出其大小,就意味着它有空间用于未使用的项目。

这也不是真的。动态数组不会自动调整大小。必须通过调用SetLength显式调整它们的大小。 TList<T>类使用对SetLength的调用来管理存储列表内容的基础动态数组的容量。

  

为什么List不是实际的枚举器?

如果我记得,最近在XE3中添加了List属性。其目的是允许直接访问基础列表,这是其他方式无法实现的。

有时为了提高效率,如果您想修改列表内容,可能更愿意避免复制。例如,假设您的列表包含大小为1KB的记录。如果要在不使用List属性的情况下修改每个记录中的单个布尔值,最终会将整个1KB记录复制两次。只是修改一个布尔值。

当然,获取对底层存储的访问权限的成本是您接触到内部实现细节。 Embarcadero可以实现以更安全的方式为您提供访问权限的功能,但无论出于何种原因,他们都选择了这条路线。我想我可能已经创建了一个索引属性,它返回一个指向项目的指针。

所以,这确实是List属性的唯一用例。除非您确实需要直接访问底层存储,否则请勿使用List。当然,如果文档不愿解释任何这一点,那就太好了,但确实如此。

  

TList<T>是否有实际的枚举数?

是的。

var
  Item: SomeType;
  MyList: TList<SomeType>;
....
for Item in MyList do
  Item.Foo();

答案 1 :(得分:-1)

尝试将其更改为

for Item in oList do begin
  Writeln(Item.ClassName);
end;

不确定为什么列表可用,但不要使用它。它只是一个数组,其中一些项目可以是零。

编辑:

.List的使用失败示例:

type
  TItem = class
    text : string;
  constructor Create(AText : string);
  end;

procedure TForm1.Button1Click(Sender: TObject);
var
  oList: TObjectList<TItem>;
  Item: TItem;
begin
  oList := TObjectList<TItem>.Create;
  oList.Add(TItem.Create('a'));
  oList.Add(TItem.Create('b'));
  oList.Add(TItem.Create('c'));
  // Add items to oList
  for Item in oList.List do begin
    memo1.lines.add(item.text);
  end;
end;

constructor TItem.Create(AText: string);
begin
  self.text := AText;
end;