2014-03-28 45 views
5

TListTOjectListGenerics.Collections有一个.List属性,这是一个枚举。为什么Generics.Collections.TObjectList.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 s,则实际FList长度为4个项目,第四个(也是最后一个)项目为nil

因此,使用TObjectList.List是不安全的,因为它可能会引发访问冲突,如果您的TObjectList不具有.Count值与2(例如,1,2,4,8,16,等等的功率)。

下面的代码可能会抛出一个访问冲突:

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; 

这是不漂亮为统计员。 (你也可以检查是否Itemnil,当然。)

我的问题是这样的:

  • 为什么.List不是一个实际的枚举?
  • 还有TList/TObjectList一个实际的枚举?

这里是从TForm(其中btn1简单地增加一个线和mmo1TMemo)的例子。

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; Actual length: 4 
Found: "Line" 
Found: "Line" 
Found: "Line" 
Found: "" 
+1

你有没有意外的一段代码来证明你的理论?我的意思是,不是那种“可能”的东西,而是真的*失败。 – JensG

+0

我已经添加了一个例子。 – Svip

+0

我明白了。我只是忽略了'.List'部分,因为对于我来说这样使用列表太过分了。但你是对的。 – JensG

回答

16

TList<T>Generics.CollectionsTOjectList<T>List属性,它是一个枚举。

不,不是这样。 List属性是一个动态数组。动态数组已经构建支持枚举。

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

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

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

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

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

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

所以,这实际上是List属性的唯一用例。除非您确实需要直接访问底层存储,否则请勿使用List。当然,如果这些文档能够解释这些,那本来是很好的,但它确实存在。

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

是的。

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

谢谢。显然,我弄错了它的用法。 – Svip

+0

最终,'TObjectList .List'类似于'TList.List',它同样提供对列表内部存储的直接访问。在这两种情况下,内部存储器的大小都由“容量”属性给出,而列表的外部大小由“Count”属性给出。 –

+0

@Rob Well'TObjectList '源自'TList ',所以'List'属性在'TList '中定义并继承。 –

-1

尝试将其更改为

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

不知道为什么这个列表是可用的,但不使用它。它只是一个数组,其中一些项目可能为零。

编辑:

示例如何使用。名单可能会失败:

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; 
+1

我认为这是错过了观点。是的,总的来说,不要使用'List'属性。但是'List'的确有用处。请记住,并不是每个TList都有'T'作为一个类。当'T'是一个很大的值类型时,那么'List'在某些场景中变得很有用。这就是它被添加的原因。 –

+2

-1。所有你不得不说的关于'List'属性是不使用它,你甚至不会说为什么。你所做的所有答案都是证明已经描述过的问题的同样的问题。尽管你做出了真实的陈述,但你还没有回答所问的问题。 –