How to do for in TObjectList?
Asked Answered
W

3

7

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.

Wera answered 29/9, 2014 at 14:59 Comment(4)
use generic TObjectList<T> instead of TObjectList.Unison
Example doing it the hard way: Supporting for in loop in TObjectList descendants.Cholecyst
I reported this issue as bug 10790 in 2005. In 2011, it was "deferred to the next release."Tsan
as an "alternative" you could use two variables and absolute: var X: Pointer; y:TMyObject absolute x;. Now you can do for x in List do y.doSomeThing();Gangway
C
4

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

Cholecyst answered 29/9, 2014 at 16:33 Comment(1)
This answers the question. Although it seems plain that the answer is: just continue to use the integer index. I'm only attempting to use the for-in for fun; it doesn't really add anything.Wera
J
5

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.
Judicature answered 29/9, 2014 at 15:5 Comment(3)
You mean list := TObjectList<TCustomer>.CreateHying
Unfortunately the API being used provides a TObjectList.Wera
@DavidHeffernan oops my bad. Yes that's what I meantJudicature
C
4

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

Cholecyst answered 29/9, 2014 at 16:33 Comment(1)
This answers the question. Although it seems plain that the answer is: just continue to use the integer index. I'm only attempting to use the for-in for fun; it doesn't really add anything.Wera
H
3

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.

Hying answered 29/9, 2014 at 15:15 Comment(20)
Maybe the OP requires the use of for in loop instead or regular for loop becouse he needs the ability of removing some objects in TObjectList within that loop. If that is the case the simpliest way would be to use while loop instead of the for loop. Infact while loop is what is used within the enumerators.Designedly
@Designedly You cannot mutate the list in a for in loop any more than you can in a classic indexed for loop. Ian wants to use a for in loop because it is way more readable.Hying
@David, regarding argument 2: there is still a difference between x := TObject(o) as TMyClass and x := o as TMyClass, IMO.Heartwhole
@RudyVelthuis Yes. I'm just guessing at possible reasons. Only the RTL designers know the answer to this.Hying
@David: Ah, Ok. I think argument 3 is the most likely, since this was already the state of the code well before generics were introduced.Heartwhole
@DavidHeffernan If Ian wants to use for in loops only becouse they seems nice then my suggestion is against it. Why? Becouse all comparison tests that I have done between for in loop performance versus clasic for loop I always got poorer performance using for in loops. And their performance drop ranged from 5 and even up to 25 percent. And that is a large performance impact definitly not worth the code looking a bit nicer. I have done all the tests on Delphi XE3 so it is posible that in latest version there is lesser impact on performance.Designedly
@Silver If perf doesn't matter then why worry about it? That's known as premature optimisation. The shoddy devs at Emba implemented their enumerators as classes rather than records and so pay for heap allocation. Which costs. All my enumerators are built with records.Hying
@DavidHeffernan Could you plase share your code example of how you implement enumerators using records? I would realy like to see that.Designedly
@Silver LURD's answer shows how. It's not at all hard. The documentation is very clear.Hying
As for premature optimization. I don't think that chosing faster method which doesent require some other major changes or impose further limits could be considered as premature optimization. You talk about premature optimization when the optimization you chose enforces you with certain limitation or makes further development of your code more difficult which is not in the case when chosing for loop instead of for in loop. Infact chosing the slower method if it doesent bring you any other advantages to me seems simplay a poor decision.Designedly
@Silver the for in loop brings a clear benefit of readability and clarity over the indexed loopHying
@DavidHeffernan I haven't noticed that LURD proposes the use of enumerator using records. I will check it out in more detail when I get home and do some comparison testing to see how it performs.Designedly
@DavidHeffernan To me personally the for in loop doesen't seem any more readable than the indexed for loop. That is probably becouse I'm using old for loops from the verry start. Another reason why I prefer the use of old for loops is that the exposed index alows me to monitor the progres that has been made. That can't be done using for in loops or can it?Designedly
@Silver An indexed loop involves an extra integer loop variable and pfaffing arounf with Count-1. The for oin loop says clearly, for each item in container. Delphi does lack the Python enumerate function it is true. I don't use for in as much as I'd like because of that.Hying
Actually using enumerators which are requirement for the use of for in loops you still have integer which isn't exposed to rest of the code. And in MoveNext method you are still checking if the integer isn't larger than the number of elements-1. The main difference is that internally for in loop actually uses while loop which is checking if MoveNext method returns True. And within the MoveNext method you controll how you iterate through loop cycles, so you can for instance skip every second item, or even dynamically remove some items with which you can dynamically affect the loop cycle.Designedly
@Silver I know all this. The fact that the loop variable is hidden is the entire point though isn't it?Hying
@DavidHeffernan I kinda doubt it. Becouse if that is the main point of for in loops then I'm wandering if it was worth spending all that time writing enumerators for all of this.Designedly
@Silver Hard for you to be able to doubt what is my own personal opinion. If you prefer indexed loops that's fine. If somebody else prefers for in loops isn't that up to them? Whether or not you share that view, what other possible reason could there be for the existence of for in loops. After all, the compiler translates them in to the while loop and compiles that. So they are pure syntactic sugar. But isn't everything? Why aren't we still coding in machine code?Hying
I just wanted to express my opinion on the matter. And yes sometimes I can be a bit hard on this. But in the end evryboddy has the right to chose what he prefers.Designedly
The hig advantage of enumerators and for-in loops is that they provide a unified interface, no matter if the collection is indexable or not. The for-in loop simply loops through all items, indexed or not.Heartwhole

© 2022 - 2024 — McMap. All rights reserved.