Search code examples
delphifor-loopiteratorfor-in-looplistiterator

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.


Solution

  • 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>.