Ian Boyd
Ian Boyd

Reputation: 257153

How to do for in TObjectList?

I am trying to use for in to iterate a TObjectList:

program Project1;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  System.SysUtils, Contnrs;

var
    list: TObjectlist;
    o: TObject;
begin
    list := TObjectList.Create;
    for o in list do
    begin
        //nothing
    end;
end.

And it fails to compile:

[dcc32 Error] Project1.dpr(15): E2010 Incompatible types: 'TObject' and 'Pointer'

It seems as though Delphi's for in construct does not handle the untyped, undescended, TObjectList an as enumerable target.

How do i enumerate the objects in a TObjectList?

What i do now

My current code is:

procedure TfrmCustomerLocator.OnBatchDataAvailable(BatchList: TObjectList);
var
   i: Integer;
   o: TObject;
begin
   for i := 0 to BatchList.Count-1 do
   begin
      o := BatchList.Items[i];

      //...snip...where we do something with (o as TCustomer)
   end;
end;    

For no good reason, i was hoping to change it to:

procedure TfrmCustomerLocator.OnBatchDataAvailable(BatchList: TObjectList);
var
   o: TObject;
begin
   for o in BatchList do
   begin
      //...snip...where we do something with (o as TCustomer)
   end;
end;    

Why use an enumerator? Just cause.

Upvotes: 7

Views: 17210

Answers (3)

LU RD
LU RD

Reputation: 34947

How do i enumerate the objects in a TObjectList?

To answer the specific question, this is an example of introducing an enumerator. Note that you will have to create a descendant of TObjectList in order to add the GetEnumerator function. You could do without subclassing with a class helper, but I leave that as an exercise for the interested reader.

type

TObjectListEnumerator = record
private
  FIndex: Integer;
  FList: TObjectList;
public
  constructor Create(AList: TObjectList);
  function GetCurrent: TObject;
  function MoveNext: Boolean;
  property Current: TObject read GetCurrent;
end;

constructor TObjectListEnumerator.Create(AList: TObjectList);
begin
  FIndex := -1;
  FList := AList;
end;

function TObjectListEnumerator.GetCurrent;
begin
  Result := FList[FIndex];
end;

function TObjectListEnumerator.MoveNext: Boolean;
begin
  Result := FIndex < FList.Count - 1;
  if Result then
    Inc(FIndex);
end;

//-- Your new subclassed TObjectList

Type

TMyObjectList = class(TObjectList)
  public
    function GetEnumerator: TObjectListEnumerator;
end;

function TMyObjectList.GetEnumerator: TObjectListEnumerator;
begin
  Result := TObjectListEnumerator.Create(Self);
end;

This implementation of an enumerator uses a record instead of a class. This has the advantage of not allocating an extra object on the heap when doing for..in enumerations.

procedure TfrmCustomerLocator.OnBatchDataAvailable(BatchList: TObjectList);
var
   o: TObject;
begin
   for o in TMyObjectList(BatchList) do // A simple cast is enough in this example
   begin
      //...snip...where we do something with (o as TCustomer)
   end;
end;   

As others have noted, there is a generics class that is a better option to use, TObjectList<T>.

Upvotes: 4

Teun Pronk
Teun Pronk

Reputation: 1397

Using generics you can have a typed objectlist (just noticed the comment but ill finish this anyhow)

And if you're looking for a good reason to use a TObjectList<T> that reason will be that it saves you a lot of type casting in your code

program Project1;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  System.SysUtils, Generics.Collections;
Type
  TCustomer = class
  private
  public
    //Properties and stuff
  end;

var
    list: TObjectlist<TCustomer>;
    c: TCustomer;
begin
    list := TObjectList<TCustomer>.Create;
    for c in list do
    begin
        //nothing
    end;
end.

Upvotes: 5

David Heffernan
David Heffernan

Reputation: 613612

The enumerator for TObjectList is declared in TList, which is the class from which TObjectList is derived. The RTL doesn't bother to declare a more specific enumerator for TObjectList. So you are left with the TList enumerator. Which yields items of type Pointer, that being the type of item held by TList.

There are probably a few reasons why the RTL designers chose not to do anything with TObjectList. For example, I posit the following potential reasons:

  1. TObjectList, like everything in Contnrs is deprecated and rendered obsolete by the generic containers in Generics.Collections. Why spend resource modifying an obsolete class.
  2. Even if TObjectList had an enumerator that yielded TObject items, you'd still have to cast them. Would an enumerator that yielded TObject really be that much more helpful?
  3. The designers simply forgot that this class existed when they were adding enumerators.

What should you do. An obvious choice is to use TList<T> or TObjectList<T> from Generics.Collections. If you want to persist with TObjectList, you could sub-class it and add an enumerator that yielded TObject. In case you don't know how to do that, you can find out how to do so from the documentation. Or you could use the inherited enumerator and type cast the pointers that it yields.

It seems to me that since you are prepared to modify the code from an indexed for loop to a for in loop that implies that you are prepared to make non-trivial changes to the code. In which case the generic container would seem to be the obvious choice.

Upvotes: 3

Related Questions