Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is Generics.Collections.TObjectList.List unsafe?

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: ""
like image 774
Svip Avatar asked Mar 28 '14 13:03

Svip


People also ask

When to use generic collections in Java?

When to Use Generic Collections. Using generic collections is generally recommended, because you gain the immediate benefit of type safety without having to derive from a base collection type and implement type-specific members.

Which generic collection types do not have a non-nongeneric equivalent?

Several generic collection types do not have nongeneric counterparts. They include the following: LinkedList<T> is a general-purpose linked list that provides O (1) insertion and removal operations.

Does Delphi have generic collections like TList?

Delphi has long had generic collections, like TList<T> but it also retains the old style TList. Which should you use? Since Delphi got generic types, in the 2009 version, its runtime library (RTL) has a generic collections unit.

What happens if you use the wrong type of collection?

Using the wrong type can restrict your use of the collection. Learn how to use built-in C# iterators and how to create your own custom iterator methods. Learn about C# properties, which include features for validation, computed values, lazy evaluation, and property changed notifications. This chapter defines arrays.


Video Answer


1 Answers

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();
like image 68
David Heffernan Avatar answered Oct 04 '22 16:10

David Heffernan