TList.ForEach?

Für Fragen zur Programmiersprache auf welcher Lazarus aufbaut
Antworten
Benutzeravatar
fliegermichl
Lazarusforum e. V.
Beiträge: 1435
Registriert: Do 9. Jun 2011, 09:42
OS, Lazarus, FPC: Lazarus Fixes FPC Stable
CPU-Target: 32/64Bit
Wohnort: Echzell

TList.ForEach?

Beitrag von fliegermichl »

Im Zusammenhang mit der kleinen Artikelserie, die ich gerade schreibe, habe ich mittels

Code: Alles auswählen

  TCustomGeoList = specialize TFPGObjectList<TGeo>;

  TGeoList = class ( TCustomGeoList )
  public
    procedure Draw(Canvas : TCanvas);
  end;

implementation
procedure TGeoList.Draw(Canvas: TCanvas);
var i : integer;
  enum : TFPGListEnumeratorSpec;
begin
  enum := GetEnumerator;
  try
    while enum.MoveNext do
    begin
      geo := enum.Current;
      if geo.Visible then
      begin
        geo.PrepareCanvas(Canvas);
        geo.Draw(Canvas);
      end;
    end;
  finally
    enum.Free;
  end;
end;
eine von TFPGObjectList spezialisierte Klasse abgeleitet, welche für jedes Objekt in der Liste die Methode Draw aufruft.
Das mit diesem Enumerator ist mMn äusserst umständlich. Vor 30 Jahren hatte ich ähnliche Aufgaben mit TList.ForEach gelöst.
Da sah das dann so aus.

Code: Alles auswählen

procedure TGeoList.Draw(Canvas : TCanvas);

 procedure All(geo : TGeo);
 begin
  if (geo.Visible) then
  begin
    geo.PrepareCanvas(Canvas);
    geo.Draw(Canvas);
  end;
 end;

begin // TGeoList.Draw
 ForEach(@All);
end;
Dieser Code ruft die lokale Prozedur All für jeden in der Liste befindlichen Eintrag auf.
Ich habe spaßeshalber diese ForEach Methode mal implementiert und die funktioniert auch einwandfrei.

Code: Alles auswählen

type
 TGeoList = class ( TCustomGeoList )
     procedure ForEach (Action: Pointer; Forward : boolean = True); // Führt "Action" für jeden Eintrag der Liste aus
 end;

implementation
type
 PointerIntLocal = function(_EBP: Pointer; Param1 : Pointer; Param2 : Integer) : Pointer;

function callpointerintlocal(Func: Pointer; Frame : Pointer; Param1 : Pointer; Param2 : Integer) : Pointer;
begin
 CallPointerIntLocal := PointerIntLocal(Func)(Frame, Param1, Param2);
end;

procedure TGeoList.ForEach(Action: Pointer; Forward: boolean);
var
  item : Pointer;
  i : Integer;
begin
  if forward then i := 0 else i := Pred(Count);
  while ((forward) and (i < count)) or (not (forward) and (i >= 0)) do
  begin
    item := items[i];
    if assigned (item) then
      CallPointerIntLocal(action, get_caller_frame(get_frame), item, i);
    if forward then inc (i) else dec(i);
  end;
end;
Das Gefrickel mit CallPointerIntLocal ist notwendig, damit die aufgerufene lokale Funktion auf die Variablen und Parameter der sie umgebenden Prozedur zugreifen kann (Im obigen Beispiel der Parameter Canvas). Diesen Code hatte ich in irgendeiner Komponente gefunden, kann mich aber nicht mehr erinnern welche das war.

Meint ihr man kann die FPC Entwickler (PascalDragon?) dazu überreden, diesen Code in die TFPList / TFPGObjectList etc. aufzunehmen?
TList hatte ausserdem noch die beiden Methoden FirstThat und LastThat, die auf gleiche Weise eine lokale boolsche Funktion aufgerufen haben und den Eintrag zurücklieferten für den diese Funktion true zurückgeliefert hatte. Ich hab die beiden ebenfalls erfolgreich implementiert.

Benutzeravatar
theo
Beiträge: 10497
Registriert: Mo 11. Sep 2006, 19:01

Re: TList.ForEach?

Beitrag von theo »

Was ist der Unterschied zu:

Code: Alles auswählen

for Element in Liste do

Benutzeravatar
fliegermichl
Lazarusforum e. V.
Beiträge: 1435
Registriert: Do 9. Jun 2011, 09:42
OS, Lazarus, FPC: Lazarus Fixes FPC Stable
CPU-Target: 32/64Bit
Wohnort: Echzell

Re: TList.ForEach?

Beitrag von fliegermichl »

Wie sieht da die Syntax genau aus?

Code: Alles auswählen

procedure TGeoList.Draw(Canvas : TCanvas);
  procedure all(geo : TGeo);
  begin
    if geo.Visible then
    begin
      geo.PrepareCanvas(Canvas);
      geo.Draw(Canvas);
    end;
  end;
var geo : TGeo;
begin
 for geo in Items do all(geo);
end;
funktioniert nicht. Da bekomme ich die Fehlermeldung "ugeo.pas(177,1) Error: Wrong number of parameters specified for call to "Get""

Benutzeravatar
theo
Beiträge: 10497
Registriert: Mo 11. Sep 2006, 19:01

Re: TList.ForEach?

Beitrag von theo »

In dem Thread, den du zum Anlass für dein Tutorial genommen hast, habe ich im Beispiel für Joachim Raap "for..in" mehrfach verwendet:
viewtopic.php?p=130806#p130806

Z.B.

Code: Alles auswählen

procedure TGrpSatzList.SaveToFile(FileName: string);
...
    for AGrpSatz in Self do
      fs.Write(AGrpSatz, SizeOf(TGrpSatz));
Habe deinen Fall jetzt nicht genau angeschaut, aber eigentlich müsstest du damit auf die Lösung kommen.

PascalDragon
Beiträge: 830
Registriert: Mi 3. Jun 2020, 07:18
OS, Lazarus, FPC: L 2.0.8, FPC Trunk, OS Win/Linux
CPU-Target: Aarch64 bis Z80 ;)
Wohnort: München

Re: TList.ForEach?

Beitrag von PascalDragon »

fliegermichl hat geschrieben:
Sa 29. Okt 2022, 10:32
Ich habe spaßeshalber diese ForEach Methode mal implementiert und die funktioniert auch einwandfrei.

Code: Alles auswählen

type
 TGeoList = class ( TCustomGeoList )
     procedure ForEach (Action: Pointer; Forward : boolean = True); // Führt "Action" für jeden Eintrag der Liste aus
 end;

implementation
type
 PointerIntLocal = function(_EBP: Pointer; Param1 : Pointer; Param2 : Integer) : Pointer;

function callpointerintlocal(Func: Pointer; Frame : Pointer; Param1 : Pointer; Param2 : Integer) : Pointer;
begin
 CallPointerIntLocal := PointerIntLocal(Func)(Frame, Param1, Param2);
end;

procedure TGeoList.ForEach(Action: Pointer; Forward: boolean);
var
  item : Pointer;
  i : Integer;
begin
  if forward then i := 0 else i := Pred(Count);
  while ((forward) and (i < count)) or (not (forward) and (i >= 0)) do
  begin
    item := items[i];
    if assigned (item) then
      CallPointerIntLocal(action, get_caller_frame(get_frame), item, i);
    if forward then inc (i) else dec(i);
  end;
end;
Das Gefrickel mit CallPointerIntLocal ist notwendig, damit die aufgerufene lokale Funktion auf die Variablen und Parameter der sie umgebenden Prozedur zugreifen kann (Im obigen Beispiel der Parameter Canvas). Diesen Code hatte ich in irgendeiner Komponente gefunden, kann mich aber nicht mehr erinnern welche das war.

Meint ihr man kann die FPC Entwickler (PascalDragon?) dazu überreden, diesen Code in die TFPList / TFPGObjectList etc. aufzunehmen?
Etwas derart hackiges kommt mir nicht mal im Ansatz in die Nähe der RTL.

Die richtige Lösung ist es hier einen entsprechenden Funktionszeigertypen zu verwenden. Um geschachtelte Funktionen zu erlauben muss das entweder ein geschachtelter Funktionszeiger (procedure(aItem: T; aIndex: Integer) is nested; benötigt Modeswitch NestedProcVars) oder - seit 3.3.1 - eine Funktionsreferenz (reference to procedure(aItem: T: aIndex: Integer)) sein.

Aber solange du den Index nicht brauchst, du nur vorwärts iterieren möchtest und du auch nicht das Element ändern möchtest (zumindest im Fall von primitiven Typen oder Records), dann kannst du genauso gut for itr in cont do nutzen wie von theo vorgeschlagen.
FPC Compiler Entwickler

wp_xyz
Beiträge: 4888
Registriert: Fr 8. Apr 2011, 09:01

Re: TList.ForEach?

Beitrag von wp_xyz »

fliegermichl hat geschrieben:
Sa 29. Okt 2022, 12:32

Code: Alles auswählen

 for geo in Items do all(geo);
funktioniert nicht. Da bekomme ich die Fehlermeldung "ugeo.pas(177,1) Error: Wrong number of parameters specified for call to "Get""
"Items" einer Liste ist ein Array-Property, das heißt: man kann es nur zusammen mit einem Index verwenden: List.Items[Index]. Die Fehlermeldung zeigt, dass der Compiler verzweifelt nach dem Index sucht und ihn nicht findet...

Bei einer TStringListe würde ich (außerhalb der TStringList) so über alle enthaltenen Strings iterieren: " for s in StringList do...", das heißt, das "for" bezieht sich auf die StringListe selbst, nicht auf deren interne Liste (die von außen sowieso nicht zugänglich ist). Genauso bei der Generic-Liste hier in diesem Beitrag. Und innerhalb der List-Klasse müsstest du die Liste als "Self" ansprechen.

Daher nimm das:

Code: Alles auswählen

 for geo in Self do all(geo);

Benutzeravatar
fliegermichl
Lazarusforum e. V.
Beiträge: 1435
Registriert: Do 9. Jun 2011, 09:42
OS, Lazarus, FPC: Lazarus Fixes FPC Stable
CPU-Target: 32/64Bit
Wohnort: Echzell

Re: TList.ForEach?

Beitrag von fliegermichl »

Danke, for geo in self do all(geo) funktioniert.
Wieder etwas dazugelernt.

Antworten