2014-03-28 85 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'屬性是不使用它,你甚至不會說爲什麼。你所做的所有答案都是證明已經描述過的問題的同樣的問題。儘管你做出了真實的陳述,但你還沒有回答所問的問題。 –