为什么Generics.Collections.TObjectList.List不安全? [英] Why is Generics.Collections.TObjectList.List unsafe?

查看:114
本文介绍了为什么Generics.Collections.TObjectList.List不安全?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

TList TOjectList 位于 Generics.Collections 中有一个 .List 属性,这是一个枚举器。



例如:

  oList:= TObjectList< TItem> .Create; 
//将项目添加到oList
用于oList.List中的项目do begin
//用Item
做一些事情end;

这很整齐,但后果很严重。 .List 只读 FList TList TObjectList ),它只是一个 arrayofT



由于动态数组的大小增加了一倍,所以每当一个项目被添加超过其大小时,这意味着它有空间用于未使用的项目。



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



因此,使用 TObjectList .List 不安全,因为如果 TObjectList 没有 .Count,它可能会抛出访问冲突值为2(例如1,2,4,8,16等)。



以下代码可能会抛出访问冲突:

 用于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 isn' t是一个实际的枚举器?

  • TList / TObjectList



以下是 TForm (其中 btn1 只需添加一行, mmo1 TMemo $ b

  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;

现在,使用 string 不会抛出访问冲突。但是,当我点击3次,我得到以下内容:

  Count:3;实际长度:4 
找到:线
找到:线
找到:线
找到:


解决方案


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


没有那么简单。 List 属性是一个动态数组。动态数组支持枚举。


由于动态数组的大小在其大小增加一倍时会增加一倍,意味着它有空间用于未使用的物品。


这也不正确。动态数组不会自动调整大小。他们必须通过调用 SetLength 来显式调整大小。 TList< T> 类通过调用 SetLength 来管理存储列表内容的底层动态数组的容量。 / p>


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

<$ c $如果我记得,c> List 属性最近被添加了,在XE3中。其目的是允许直接访问可能没有其他方式的底层列表。

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

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

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


是否 TList< T> 有一个实际的枚举器?


是的。 b
$ b

  var 
Item:SomeType;
MyList:TList< SomeType>;
....
用于MyList中的Item do
Item.Foo();


TList and TOjectList in Generics.Collections have a .List property, which is an enumerator.

For instance:

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

This is neat, but has a drastic consequence. .List just reads FList (a private declaration on TList and TObjectList), which is merely an arrayofT.

Since a dynamic array is doubled in size whenever an item is added beyond its size, it means it has space for non-used items.

In case you have added 3 TItems, the actual FList is 4 items long, with the fourth (and last) item being nil.

Thus using TObjectList's .List is unsafe, because it will likely throw an access violation, if your TObjectList doesn't have a .Count value with a power of 2 (e.g. 1, 2, 4, 8, 16, etc.).

The following code could likely throw an access violation:

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

Of course, the safe solution is a simple iteration using .Count:

for I := 0 to oList.Count - 1 do begin
  Item := oList.Items[I];
  Writeln(Item.ClassName);
end;

This is just not as pretty as the enumerator. (You can also check if Item is nil, of course.)

My questions are thus:

  • Why .List isn't an actual enumerator?
  • And does TList/TObjectList have an actual enumerator?

Here is an example from a TForm (where btn1 simply adds a line and mmo1 is a 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;

Now, using string does not throw an access violation. But when I have clicked 3 times, I get the following:

Count: 3; Actual length: 4
Found: "Line"
Found: "Line"
Found: "Line"
Found: ""

解决方案

TList<T> and TOjectList<T> in Generics.Collections have a List property, which is an enumerator.

No that is not so. The List property is a dynamic array. And dynamic arrays have built in support for enumeration.

Since a dynamic array is doubled in size whenever an item is added beyond its size, it means it has space for non-used items.

That is also not true. Dynamic arrays are not automatically resized. They have to be explicitly resized with a call to SetLength. The TList<T> class manages capacity of the underlying dynamic array that stores the list content using calls to SetLength.

Why is List not an actual enumerator?

The List property was added recently, in XE3 if I recall. Its purpose is to allow direct access to the underlying list which is possible no other way.

Sometimes for the sake of efficiency you might prefer to avoid taking copies if you want to modify the content of the list. For instance suppose your list contains records that are 1KB in size. If you want to modify a single boolean within each record, without using the List property, you end up copying the entire 1KB record twice. Just to modify one boolean.

Of course the cost for gaining access to the underlying storage is that you are exposed to the internal implementation details. Embarcadero could have implemented functionality that would give you this access in a safer way but for whatever reason they chose this route. I think I might have made an indexed property that returns a pointer to an item.

So, that is really the only use case for the List property. Unless you really need direct access to the underlying storage, do not use List. Of course, it would have been nice had the documentation bothered to explain any of this, but there it is.

Does TList<T> have an actual enumerator?

Yes it does.

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

这篇关于为什么Generics.Collections.TObjectList.List不安全?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆