for-in loop/fr
│
English (en) │
français (fr) │
日本語 (ja) │
русский (ru) │
La construction de boucle "for-in" est pris en charge dans Delphi de Delphi 2005. Il a été mis en œuvre en FPC 2.4.2.
Implémentation Delphi et FPC
Une boucle for in respecte la syntaxe suivante:
Boucle sur une chaîne
procedure StringLoop(S: String);
var
C: Char;
begin
for C in S do
DoSomething(C);
end;
Boucle sur un tableau
procedure ArrayLoop(A: Array of Byte);
var
B: Byte;
begin
for B in A do
DoSomething(B);
end;
Boucle sur un ensemble
type
TColor = (cRed, cGren, cBlue);
TColors = set of TColor;
procedure SetLoop(Colors: TColors);
var
Color: TColor;
begin
for Color in Colors do
DoSomething(Color);
end;
Traverser un conteneur
Pour travrser une classe conteneur, vous avez besoin d'ajouter un énumérateur. Un Enumérateur est une classe construite selon le modèle suivant :
TSomeEnumerator = class
public
function MoveNext: Boolean;
property Current: TSomeType;
end;
Il faut seulement 2 choses pour définir une classe Enumerateur: une méthode MoveNext qui demande à l'énumérateur d'avancer d'un pas et une propriété Current qui peut retourner tout type approprié.
Par la suite, vous devez ajouter la méthode magique GetEnumerator de la classe conteneur qui retourne un énumérateur de l'instance.
Par exemple:
type
TEnumerableTree = class;
TTreeEnumerator = class
private
FTree: TEnumerableTree;
FCurrent: TNode;
public
constructor Create(ATree: TEnumerableTree);
function MoveNext: Boolean;
property Current: TNode read FCurrent;
end;
TEnumerableTree = class
public
function GetEnumerator: TTreeEnumerator;
end;
constructor TTreeEnumerator.Create(ATree: TEnumerableTree);
begin
inherited Create;
FTree := ATree;
FCurrent := nil;
end;
function TTreeEnumerator.MoveNext: Boolean;
begin
// pour obtenir le noeud suivant dans l'arbre
if FCurrent = nil then
FCurrent := FTree.GetFirstNode
else
FCurrent := FTree.GetNextNode(FCurrent);
Result := FCurrent <> nil;
end;
function TEnumerableTree.GetEnumerator: TTreeEnumerator;
begin
Result := TTreeEnumerator.Create(Self);
// Important: l'objet créé est automatiquement libéré par le compilateur après la boucle.
end;
Ensuite, cela vous permet de d'exécuter le code suivant:
procedure TreeLoop(ATree: TEnumerableTree);
var
ANode: TNode;
begin
for ANode in ATree do
DoSomething(ANode);
end;
Vos trouverez que plusieurs classes de bases (telles que TList, TStrings, TCollection, TComponent ...) ont des énumérateurs intégrés.
Il est aussi possible de rendre une classe énumérable si vous implémentez l'interface suivante dans votre classe conteneur enumérable:
IEnumerable = interface(IInterface)
function GetEnumerator: IEnumerator;
end;
ooù IEnumerator est déclarée comme :
IEnumerator = interface(IInterface)
function GetCurrent: TObject;
function MoveNext: Boolean;
procedure Reset;
property Current: TObject read GetCurrent;
end;
Enumérateurs multiple pour une classe
Vous pouvez ajouter des énumérateur supplémentaires à des classes, des objets et des enregistrements.
Voici un exemple d'ajout d'énumérateur qui traverse un TEnumerableTree en ordre inverse:
type
TEnumerableTree = class;
TTreeEnumerator = class
// ...pour la traversée dans l'ordre, voir au dessus...
end;
TTreeReverseEnumerator = class
private
FTree: TEnumerableTree;
FCurrent: TNode;
public
constructor Create(ATree: TEnumerableTree);
function MoveNext: Boolean;
property Current: TNode read FCurrent;
function GetEnumerator: TTreeReverseEnumerator; // se retourne lui-même
end;
TEnumerableTree = class
public
function GetEnumerator: TTreeEnumerator;
function GetReverseEnumerator: TTreeReverseEnumerator;
end;
//...voir au dessus l'implémentation de TTreeEnumerator...
constructor TTreeReverseEnumerator.Create(ATree: TEnumerableTree);
begin
inherited Create;
FTree := ATree;
end;
function TTreeReverseEnumerator.MoveNext: Boolean;
begin
// some logic to get the next node from a tree in reverse order
if FCurrent = nil then
FCurrent := FTree.GetLastNode
else
FCurrent := FTree.GetPrevNode(FCurrent);
Result := FCurrent <> nil;
end;
function TTreeReverseEnumerator.GetEnumerator: TTreeReverseEnumerator;
begin
Result := Self;
end;
function TEnumerableTree.GetReverseEnumerator: TTreeReverseEnumerator;
begin
Result := TTreeReverseEnumerator.Create(Self);
// Note: l'objet est automatiquement supprimé par le compilateur après la boucle.
end;
Après, vous êtes en mesure d'exécuter le code suivant:
procedure TreeLoop(ATree: TEnumerableTree);
var
ANode: TNode;
begin
for ANode in ATree.GetReverseEnumerator do
DoSomething(ANode);
end;
Extensions FPC
Les exemples de code suivants illustrent les constructions implémentées uniquement dans FPC, elles ne sont pas supportées par Delphi.
Traversée de types énumération et intervalle
Dans Delphi, il n'est pas possible de traverser non plus les types énumérés ou les intervalles, alors qu'en Free Pascal vous pouvez écrire ce qui suit:
type
TColor = (clRed, clBlue, clBlack);
TRange = 'a'..'z';
var
Color: TColor;
ch: Char;
begin
for Color in TColor do
DoSomething(Color);
for ch in TRange do
DoSomethingOther(ch);
end.
Déclarer des énumérateurs
Il n'est pas non plus possible dans Delphi d'ajouter un énumérateur sans modifier la classe, ni d'ajouter un énumérateur d'un type non class object/record/interface. FPC rend cela possible en utilisant la nouvelle syntaxe operator type Enumerator. Comme dans l'exemple suivant:
type
TMyRecord = record F1: Integer; F2: array of TMyType; end;
TMyArrayEnumerator = class
private
function GetCurrent: TMyType;
public
constructor Create(const A: TMyRecord);
property Current: TMyType read GetCurrent;
function MoveNext: Boolean;
end;
// C'est le nouveau opérateur intégré.
operator Enumerator(const A: TMyRecord): TMyArrayEnumerator;
begin
Result := TMyArrayEnumerator.Create(A);
end;
var
A: MyRecord;
V: TMyType
begin
for V in A do
DoSomething(V);
end.
TRraversée de chaînes UTF-8
Comme exemple particulièrement utile, l'extension du dessous autorise une traversée très efficace des chaînes UTF-8:
uses
LazUTF8;
interface
type
{ TUTF8StringAsStringEnumerator
La traversée de 'codepoint' UTF8 est utile quand vous voulez connaître l'encodage exact d'un
caractère UTF-8 ou si vous aimez utiliser des constante de chaîne dans votre code.
Pour des raisons de sécurité, vous pourriez utiliser les valeurs des points de code (cardinaux) à la place.
Si la vitesse importe, n'utilisez pas des énumérateurs. Employez à la place directement le PChar
comme montré dans la méthode MoveNext et lisez sur l'UTF8. Il a des caractéristiques intéressantes. }
TUTF8StringAsStringEnumerator = class
private
fCurrent: UTF8String;
fCurrentPos, fEndPos: PChar;
function GetCurrent: UTF8String;
public
constructor Create(const A: UTF8String);
property Current: UTF8String read GetCurrent;
function MoveNext: Boolean;
end;
operator Enumerator(A: UTF8String): TUTF8StringAsStringEnumerator;
var
Form1: TForm1;
implementation
operator Enumerator(A: UTF8String): TUTF8StringAsStringEnumerator;
begin
Result := TUTF8StringAsStringEnumerator.Create(A);
end;
{ TUTF8StringAsStringEnumerator }
function TUTF8StringAsStringEnumerator.GetCurrent: UTF8String;
begin
Result:=fCurrent;
end;
constructor TUTF8StringAsStringEnumerator.Create(const A: UTF8String);
begin
fCurrentPos:=PChar(A); // Note: si A='' alors PChar(A) retourne un pointeur sur une chaîne #0
fEndPos:=fCurrentPos+length(A);
end;
function TUTF8StringAsStringEnumerator.MoveNext: Boolean;
var
l: Integer;
begin
if fCurrentPos<fEndPos then
begin
l:=UTF8CharacterLength(fCurrentPos);
SetLength(fCurrent,l);
Move(fCurrentPos^,fCurrent[1],l);
inc(fCurrentPos,l);
Result:=true;
end else
Result:=false;
end;
{ TForm1 }
procedure TForm1.FormCreate(Sender: TObject);
var
s, ch: UTF8String;
i: SizeInt;
begin
s:='mäßig';
// Utiliser UTF8Length et UTF8Copy de cette manière est très lent, nécessitant O(n)^2
for i:=1 to UTF8Length(s) do
writeln('ch=',UTF8Copy(s,i,1));
// L'emploi de l'énumérateur du dessus est plus court et plutôt rapide, nécessitant O(n)
for ch in s do
writeln('ch=',ch);
end;
Utiliser des identificateurs au lieu de MoveNext et Current
En Delphi, vous devez utiliser une fonction avec le nom 'MoveNext' et une propriété avec le nom 'Current' dans les énumérateurs. Avec FPC, vous pouvez choisir n'importe quel nom. Cela est permis par l'emploi du modifieur enumerator, avec la syntaxe des modifieurs 'enumerator MoveNext;' et 'enumerator Current;'. Comme dans l'exemple suivant :
type
{ TMyListEnumerator }
TMyListEnumerator = object
private
FCurrent: Integer;
public
constructor Create;
destructor Destroy;
function StepNext: Boolean; enumerator MoveNext;
property Value: Integer read FCurrent; enumerator Current;
end;
TMyList = class
end;
{ TMyListEnumerator }
constructor TMyListEnumerator.Create;
begin
FCurrent := 0;
end;
destructor TMyListEnumerator.Destroy;
begin
inherited;
end;
function TMyListEnumerator.StepNext: Boolean;
begin
inc(FCurrent);
Result := FCurrent <= 3;
end;
operator enumerator (AList: TMyList): TMyListEnumerator;
begin
Result.Create;
end;
var
List: TMyList;
i: integer;
begin
List := TMyList.Create;
for i in List do
WriteLn(i);
List.Free;
end.
Extensions proposées
Donner la position de l'énumérateur si disponible
Il est impossible d'extraire toute information de l'itérateur sauf l'article courant. Parfois d'autres informations, comme l'index courant pourrait être utile :
type
TUTF8StringEnumerator = class
private
FByteIndex: Integer;
FCharIndex: Integer;
public
constructor Create(const A: UTF8String);
function Current: UTF8Char;
function CurrentIndex: Integer;
function MoveNext: Boolean;
end;
operator GetEnumerator(A: UTF8String): TUTF8StringEnumerator;
begin
Result := TUTF8String.Create(A);
end;
var
s: UTF8String;
ch: UTF8Char;
i: Integer;
begin
// inefficace comme discuté plus haut
for i := 1 to Length(s) do
Writeln(i, ': ', ch[i]);
// Ok, mais moche
i := 1;
for ch in s do begin
Writeln(i, ': ', ch);
Inc(i);
end;
// Extension proposée
for ch in s index i do
Writeln(i, ': ', ch);
// Extension proposée pour traverser à l'envers (équivalent au downto)
for ch in reverse s do
Writeln(i, ': ', ch);
// Avec l'extension d'index proposée
for ch in reverse s index i do
Writeln(i, ': ', ch);
end.
Remarquez que l'index doit être conçu pour retourner un type arbitraire (i.e. pas forcément un entier). Par exemple, dans l'exemple d'une traversée d'arbre, l'index pourrait retourner un tableau de noeuds décrivant le chemin allant de la racine au noeud courant.