for-in loop/fr: what changed
Revision 0 to 1. Removed lines have a minus sign; added lines have a plus sign.
Title: for-in loop/fr
| Old | New | Line | |
|---|---|---|---|
| 1 | + | La construction de boucle "for-in" est prise en charge dans Delphi depuis Delphi 2005. Elle a été mise en œuvre dans FPC 2.4.2. | |
| 2 | + | ||
| 3 | + | ## Contents | |
| 4 | + | ||
| 5 | + | * 1 Implémentation Delphi et FPC | |
| 6 | + | * 1.1 Boucle sur une chaîne | |
| 7 | + | * 1.2 Boucle sur un tableau | |
| 8 | + | * 1.3 Boucle sur un ensemble | |
| 9 | + | * 1.4 Traverser un conteneur | |
| 10 | + | * 1.5 Enumérateurs multiples pour une classe | |
| 11 | + | * 2 Extensions FPC | |
| 12 | + | * 2.1 Traversée de types énumération et intervalle | |
| 13 | + | * 2.2 Déclarer des énumérateurs | |
| 14 | + | * 2.2.1 Traversée de chaînes UTF-8 | |
| 15 | + | * 2.3 Utiliser des identificateurs au lieu de MoveNext et Current | |
| 16 | + | * 3 Extensions proposées | |
| 17 | + | * 3.1 Donner la position de l'énumérateur si disponible | |
| 18 | + | * 3.1.1 Problématique | |
| 19 | + | * 3.1.2 Solution de contournement pour l'indice | |
| 20 | + | * 4 Référence | |
| 21 | + | ||
| 22 | + | # Implémentation Delphi et FPC | |
| 23 | + | ||
| 24 | + | Une boucle **for in** respecte la syntaxe suivante: | |
| 25 | + | ||
| 26 | + | ## Boucle sur une chaîne | |
| 27 | + | ``` | |
| 28 | + | procedure StringLoop(S: String); | |
| 29 | + | var | |
| 30 | + | C: Char; | |
| 31 | + | begin | |
| 32 | + | for C in S do | |
| 33 | + | DoSomething(C); | |
| 34 | + | end; | |
| 35 | + | ``` | |
| 36 | + | ||
| 37 | + | ## Boucle sur un tableau | |
| 38 | + | ``` | |
| 39 | + | procedure ArrayLoop(A: Array of Byte); | |
| 40 | + | var | |
| 41 | + | B: Byte; | |
| 42 | + | begin | |
| 43 | + | for B in A do | |
| 44 | + | DoSomething(B); | |
| 45 | + | end; | |
| 46 | + | ``` | |
| 47 | + | ||
| 48 | + | ## Boucle sur un ensemble | |
| 49 | + | ``` | |
| 50 | + | type | |
| 51 | + | TColor = (cRed, cGren, cBlue); | |
| 52 | + | TColors = set of TColor; | |
| 53 | + | procedure SetLoop(Colors: TColors); | |
| 54 | + | var | |
| 55 | + | Color: TColor; | |
| 56 | + | begin | |
| 57 | + | for Color in Colors do | |
| 58 | + | DoSomething(Color); | |
| 59 | + | end; | |
| 60 | + | ``` | |
| 61 | + | ||
| 62 | + | ## Traverser un conteneur | |
| 63 | + | ||
| 64 | + | 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 : | |
| 65 | + | ``` | |
| 66 | + | TSomeEnumerator = class | |
| 67 | + | public | |
| 68 | + | function MoveNext: Boolean; | |
| 69 | + | property Current: TSomeType; | |
| 70 | + | end; | |
| 71 | + | ``` | |
| 72 | + | ||
| 73 | + | Il faut seulement deux 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é. | |
| 74 | + | ||
| 75 | + | Par la suite, vous devez ajouter la méthode magique **GetEnumerator** de la classe conteneur qui retourne un **énumérateur** de l'instance. | |
| 76 | + | ||
| 77 | + | Par exemple: | |
| 78 | + | ``` | |
| 79 | + | type | |
| 80 | + | TEnumerableTree = class; | |
| 81 | + | ||
| 82 | + | TTreeEnumerator = class | |
| 83 | + | private | |
| 84 | + | FTree: TEnumerableTree; | |
| 85 | + | FCurrent: TNode; | |
| 86 | + | public | |
| 87 | + | constructor Create(ATree: TEnumerableTree); | |
| 88 | + | function MoveNext: Boolean; | |
| 89 | + | property Current: TNode read FCurrent; | |
| 90 | + | end; | |
| 91 | + | ||
| 92 | + | TEnumerableTree = class | |
| 93 | + | public | |
| 94 | + | function GetEnumerator: TTreeEnumerator; | |
| 95 | + | end; | |
| 96 | + | ||
| 97 | + | constructor TTreeEnumerator.Create(ATree: TEnumerableTree); | |
| 98 | + | begin | |
| 99 | + | inherited Create; | |
| 100 | + | FTree := ATree; | |
| 101 | + | FCurrent := nil; | |
| 102 | + | end; | |
| 103 | + | ||
| 104 | + | function TTreeEnumerator.MoveNext: Boolean; | |
| 105 | + | begin | |
| 106 | + | // pour obtenir le nœud suivant dans l'arbre | |
| 107 | + | if FCurrent = nil then | |
| 108 | + | FCurrent := FTree.GetFirstNode | |
| 109 | + | else | |
| 110 | + | FCurrent := FTree.GetNextNode(FCurrent); | |
| 111 | + | Result := FCurrent <> nil; | |
| 112 | + | end; | |
| 113 | + | ||
| 114 | + | function TEnumerableTree.GetEnumerator: TTreeEnumerator; | |
| 115 | + | begin | |
| 116 | + | Result := TTreeEnumerator.Create(Self); | |
| 117 | + | // Important: l'objet créé est automatiquement libéré par le compilateur après la boucle. | |
| 118 | + | end; | |
| 119 | + | ``` | |
| 120 | + | ||
| 121 | + | Ensuite, cela vous permet de d'exécuter le code suivant: | |
| 122 | + | ``` | |
| 123 | + | procedure TreeLoop(ATree: TEnumerableTree); | |
| 124 | + | var | |
| 125 | + | ANode: TNode; | |
| 126 | + | begin | |
| 127 | + | for ANode in ATree do | |
| 128 | + | DoSomething(ANode); | |
| 129 | + | end; | |
| 130 | + | ``` | |
| 131 | + | ||
| 132 | + | Vos trouverez que plusieurs classes de bases (telles que TList, TStrings, TCollection, TComponent ...) ont des énumérateurs intégrés. | |
| 133 | + | ||
| 134 | + | Il est aussi possible de rendre une classe énumérable si vous implémentez l'interface suivante dans votre classe conteneur enumérable: | |
| 135 | + | ``` | |
| 136 | + | IEnumerable = interface(IInterface) | |
| 137 | + | function GetEnumerator: IEnumerator; | |
| 138 | + | end; | |
| 139 | + | ``` | |
| 140 | + | ||
| 141 | + | où **IEnumerator** est déclarée comme : | |
| 142 | + | ``` | |
| 143 | + | IEnumerator = interface(IInterface) | |
| 144 | + | function GetCurrent: TObject; | |
| 145 | + | function MoveNext: Boolean; | |
| 146 | + | procedure Reset; | |
| 147 | + | property Current: TObject read GetCurrent; | |
| 148 | + | end; | |
| 149 | + | ``` | |
| 150 | + | ||
| 151 | + | ## Enumérateurs multiples pour une classe | |
| 152 | + | ||
| 153 | + | Vous pouvez ajouter des énumérateurs supplémentaires à des classes, des objets et des enregistrements. | |
| 154 | + | ||
| 155 | + | Voici un exemple d'ajout d'énumérateur qui traverse un TEnumerableTree en ordre inverse: | |
| 156 | + | ``` | |
| 157 | + | type | |
| 158 | + | TEnumerableTree = class; | |
| 159 | + | ||
| 160 | + | TTreeEnumerator = class | |
| 161 | + | // ...pour la traversée dans l'ordre, voir ci-dessus... | |
| 162 | + | end; | |
| 163 | + | ||
| 164 | + | TTreeReverseEnumerator = class | |
| 165 | + | private | |
| 166 | + | FTree: TEnumerableTree; | |
| 167 | + | FCurrent: TNode; | |
| 168 | + | public | |
| 169 | + | constructor Create(ATree: TEnumerableTree); | |
| 170 | + | function MoveNext: Boolean; | |
| 171 | + | property Current: TNode read FCurrent; | |
| 172 | + | function GetEnumerator: TTreeReverseEnumerator; // se retourne lui-même | |
| 173 | + | end; | |
| 174 | + | ||
| 175 | + | TEnumerableTree = class | |
| 176 | + | public | |
| 177 | + | function GetEnumerator: TTreeEnumerator; | |
| 178 | + | function GetReverseEnumerator: TTreeReverseEnumerator; | |
| 179 | + | end; | |
| 180 | + | ||
| 181 | + | //...voir ci-dessus l'implémentation de TTreeEnumerator... | |
| 182 | + | ||
| 183 | + | constructor TTreeReverseEnumerator.Create(ATree: TEnumerableTree); | |
| 184 | + | begin | |
| 185 | + | inherited Create; | |
| 186 | + | FTree := ATree; | |
| 187 | + | end; | |
| 188 | + | ||
| 189 | + | function TTreeReverseEnumerator.MoveNext: Boolean; | |
| 190 | + | begin | |
| 191 | + | // some logic to get the next node from a tree in reverse order | |
| 192 | + | if FCurrent = nil then | |
| 193 | + | FCurrent := FTree.GetLastNode | |
| 194 | + | else | |
| 195 | + | FCurrent := FTree.GetPrevNode(FCurrent); | |
| 196 | + | Result := FCurrent <> nil; | |
| 197 | + | end; | |
| 198 | + | ||
| 199 | + | function TTreeReverseEnumerator.GetEnumerator: TTreeReverseEnumerator; | |
| 200 | + | begin | |
| 201 | + | Result := Self; | |
| 202 | + | end; | |
| 203 | + | ||
| 204 | + | function TEnumerableTree.GetReverseEnumerator: TTreeReverseEnumerator; | |
| 205 | + | begin | |
| 206 | + | Result := TTreeReverseEnumerator.Create(Self); | |
| 207 | + | // Note: l'objet est automatiquement supprimé par le compilateur après la boucle. | |
| 208 | + | end; | |
| 209 | + | ``` | |
| 210 | + | ||
| 211 | + | Ensuite vous êtes en mesure d'exécuter le code suivant: | |
| 212 | + | ``` | |
| 213 | + | procedure TreeLoop(ATree: TEnumerableTree); | |
| 214 | + | var | |
| 215 | + | ANode: TNode; | |
| 216 | + | begin | |
| 217 | + | for ANode in ATree.GetReverseEnumerator do | |
| 218 | + | DoSomething(ANode); | |
| 219 | + | end; | |
| 220 | + | ``` | |
| 221 | + | ||
| 222 | + | # Extensions FPC | |
| 223 | + | ||
| 224 | + | Les exemples de code suivants illustrent les constructions implémentées uniquement dans FPC ; elles ne sont pas supportées par Delphi. | |
| 225 | + | ||
| 226 | + | ## Traversée de types énumération et intervalle | |
| 227 | + | ||
| 228 | + | Dans Delphi, il n'est [pas possible de traverser non plus les types énumérés](<http://qc.embarcadero.com/wc/qcmain.aspx?d=106212>) ou les intervalles, alors qu'en Free Pascal vous pouvez écrire ce qui suit: | |
| 229 | + | ``` | |
| 230 | + | type | |
| 231 | + | TColor = (clRed, clBlue, clBlack); | |
| 232 | + | TRange = 'a'..'z'; | |
| 233 | + | var | |
| 234 | + | Color: TColor; | |
| 235 | + | ch: Char; | |
| 236 | + | begin | |
| 237 | + | for Color in TColor do | |
| 238 | + | DoSomething(Color); | |
| 239 | + | for ch in TRange do | |
| 240 | + | DoSomethingOther(ch); | |
| 241 | + | end. | |
| 242 | + | ``` | |
| 243 | + | ||
| 244 | + | ## Déclarer des énumérateurs | |
| 245 | + | ||
| 246 | + | 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: | |
| 247 | + | ``` | |
| 248 | + | type | |
| 249 | + | TMyRecord = record F1: Integer; F2: array of TMyType; end; | |
| 250 | + | TMyArrayEnumerator = class | |
| 251 | + | private | |
| 252 | + | function GetCurrent: TMyType; | |
| 253 | + | public | |
| 254 | + | constructor Create(const A: TMyRecord); | |
| 255 | + | property Current: TMyType read GetCurrent; | |
| 256 | + | function MoveNext: Boolean; | |
| 257 | + | end; | |
| 258 | + | ||
| 259 | + | // C'est le nouveau opérateur intégré. | |
| 260 | + | operator Enumerator(const A: TMyRecord): TMyArrayEnumerator; | |
| 261 | + | begin | |
| 262 | + | Result := TMyArrayEnumerator.Create(A); | |
| 263 | + | end; | |
| 264 | + | ||
| 265 | + | var | |
| 266 | + | A: MyRecord; | |
| 267 | + | V: TMyType | |
| 268 | + | begin | |
| 269 | + | for V in A do | |
| 270 | + | DoSomething(V); | |
| 271 | + | end. | |
| 272 | + | ``` | |
| 273 | + | ||
| 274 | + | ### Traversée de chaînes UTF-8 | |
| 275 | + | ||
| 276 | + | Comme exemple particulièrement utile, l'extension ci-dessous autorise une traversée très efficace des chaînes UTF-8: | |
| 277 | + | ``` | |
| 278 | + | uses | |
| 279 | + | LazUTF8; | |
| 280 | + | interface | |
| 281 | + | type | |
| 282 | + | { TUTF8StringAsStringEnumerator | |
| 283 | + | La traversée de 'codepoint' UTF8 est utile quand vous voulez connaître l'encodage exact d'un | |
| 284 | + | caractère UTF-8 ou si vous aimez utiliser des constante de chaîne dans votre code. | |
| 285 | + | Pour des raisons de sécurité, vous pourriez utiliser les valeurs des points de code (cardinaux) à la place. | |
| 286 | + | Si la vitesse importe, n'utilisez pas des énumérateurs. Employez à la place directement le PChar | |
| 287 | + | comme montré dans la méthode MoveNext et lisez sur l'UTF8. Il a des caractéristiques intéressantes. } | |
| 288 | + | ||
| 289 | + | TUTF8StringAsStringEnumerator = class | |
| 290 | + | private | |
| 291 | + | fCurrent: UTF8String; | |
| 292 | + | fCurrentPos, fEndPos: PChar; | |
| 293 | + | function GetCurrent: UTF8String; | |
| 294 | + | public | |
| 295 | + | constructor Create(const A: UTF8String); | |
| 296 | + | property Current: UTF8String read GetCurrent; | |
| 297 | + | function MoveNext: Boolean; | |
| 298 | + | end; | |
| 299 | + | ||
| 300 | + | operator Enumerator(A: UTF8String): TUTF8StringAsStringEnumerator; | |
| 301 | + | ||
| 302 | + | var | |
| 303 | + | Form1: TForm1; | |
| 304 | + | ||
| 305 | + | implementation | |
| 306 | + | ||
| 307 | + | operator Enumerator(A: UTF8String): TUTF8StringAsStringEnumerator; | |
| 308 | + | begin | |
| 309 | + | Result := TUTF8StringAsStringEnumerator.Create(A); | |
| 310 | + | end; | |
| 311 | + | ||
| 312 | + | { TUTF8StringAsStringEnumerator } | |
| 313 | + | ||
| 314 | + | function TUTF8StringAsStringEnumerator.GetCurrent: UTF8String; | |
| 315 | + | begin | |
| 316 | + | Result:=fCurrent; | |
| 317 | + | end; | |
| 318 | + | ||
| 319 | + | constructor TUTF8StringAsStringEnumerator.Create(const A: UTF8String); | |
| 320 | + | begin | |
| 321 | + | fCurrentPos:=PChar(A); // Note: si A='' alors PChar(A) retourne un pointeur sur une chaîne #0 | |
| 322 | + | fEndPos:=fCurrentPos+length(A); | |
| 323 | + | end; | |
| 324 | + | ||
| 325 | + | function TUTF8StringAsStringEnumerator.MoveNext: Boolean; | |
| 326 | + | var | |
| 327 | + | l: Integer; | |
| 328 | + | begin | |
| 329 | + | if fCurrentPos<fEndPos then | |
| 330 | + | begin | |
| 331 | + | l:=UTF8CharacterLength(fCurrentPos); | |
| 332 | + | SetLength(fCurrent,l); | |
| 333 | + | Move(fCurrentPos^,fCurrent[1],l); | |
| 334 | + | inc(fCurrentPos,l); | |
| 335 | + | Result:=true; | |
| 336 | + | end else | |
| 337 | + | Result:=false; | |
| 338 | + | end; | |
| 339 | + | ||
| 340 | + | { TForm1 } | |
| 341 | + | ||
| 342 | + | procedure TForm1.FormCreate(Sender: TObject); | |
| 343 | + | var | |
| 344 | + | s, ch: UTF8String; | |
| 345 | + | i: SizeInt; | |
| 346 | + | begin | |
| 347 | + | s:='mäßig'; | |
| 348 | + | ||
| 349 | + | // Utiliser UTF8Length et UTF8Copy de cette manière est très lent, nécessitant O(n)^2 | |
| 350 | + | for i:=1 to UTF8Length(s) do | |
| 351 | + | writeln('ch=',UTF8Copy(s,i,1)); | |
| 352 | + | ||
| 353 | + | // L'emploi de l'énumérateur du dessus est plus court et plutôt rapide, nécessitant O(n) | |
| 354 | + | for ch in s do | |
| 355 | + | writeln('ch=',ch); | |
| 356 | + | end; | |
| 357 | + | ``` | |
| 358 | + | ||
| 359 | + | ## Utiliser des identificateurs au lieu de MoveNext et Current | |
| 360 | + | ||
| 361 | + | 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 : | |
| 362 | + | ``` | |
| 363 | + | type | |
| 364 | + | { TMyListEnumerator } | |
| 365 | + | ||
| 366 | + | TMyListEnumerator = object | |
| 367 | + | private | |
| 368 | + | FCurrent: Integer; | |
| 369 | + | public | |
| 370 | + | constructor Create; | |
| 371 | + | destructor Destroy; | |
| 372 | + | function StepNext: Boolean; enumerator MoveNext; | |
| 373 | + | property Value: Integer read FCurrent; enumerator Current; | |
| 374 | + | end; | |
| 375 | + | ||
| 376 | + | TMyList = class | |
| 377 | + | end; | |
| 378 | + | ||
| 379 | + | { TMyListEnumerator } | |
| 380 | + | ||
| 381 | + | constructor TMyListEnumerator.Create; | |
| 382 | + | begin | |
| 383 | + | FCurrent := 0; | |
| 384 | + | end; | |
| 385 | + | ||
| 386 | + | destructor TMyListEnumerator.Destroy; | |
| 387 | + | begin | |
| 388 | + | inherited; | |
| 389 | + | end; | |
| 390 | + | ||
| 391 | + | function TMyListEnumerator.StepNext: Boolean; | |
| 392 | + | begin | |
| 393 | + | inc(FCurrent); | |
| 394 | + | Result := FCurrent <= 3; | |
| 395 | + | end; | |
| 396 | + | ||
| 397 | + | operator enumerator (AList: TMyList): TMyListEnumerator; | |
| 398 | + | begin | |
| 399 | + | Result.Create; | |
| 400 | + | end; | |
| 401 | + | ||
| 402 | + | var | |
| 403 | + | List: TMyList; | |
| 404 | + | i: integer; | |
| 405 | + | begin | |
| 406 | + | List := TMyList.Create; | |
| 407 | + | for i in List do | |
| 408 | + | WriteLn(i); | |
| 409 | + | List.Free; | |
| 410 | + | end. | |
| 411 | + | ``` | |
| 412 | + | ||
| 413 | + | # Extensions proposées | |
| 414 | + | ||
| 415 | + | ## Donner la position de l'énumérateur si disponible | |
| 416 | + | ||
| 417 | + | ### Problématique | |
| 418 | + | ||
| 419 | + | Il est impossible d'extraire toute information de l'itérateur sauf l'article courant. Parfois d'autres informations, comme l'index courant, pourraient être utiles : | |
| 420 | + | ``` | |
| 421 | + | type | |
| 422 | + | TUTF8StringEnumerator = class | |
| 423 | + | private | |
| 424 | + | FByteIndex: Integer; | |
| 425 | + | FCharIndex: Integer; | |
| 426 | + | public | |
| 427 | + | constructor Create(const A: UTF8String); | |
| 428 | + | function Current: UTF8Char; | |
| 429 | + | function CurrentIndex: Integer; | |
| 430 | + | function MoveNext: Boolean; | |
| 431 | + | end; | |
| 432 | + | ||
| 433 | + | operator GetEnumerator(A: UTF8String): TUTF8StringEnumerator; | |
| 434 | + | begin | |
| 435 | + | Result := TUTF8String.Create(A); | |
| 436 | + | end; | |
| 437 | + | ||
| 438 | + | var | |
| 439 | + | s: UTF8String; | |
| 440 | + | ch: UTF8Char; | |
| 441 | + | i: Integer; | |
| 442 | + | begin | |
| 443 | + | ||
| 444 | + | // inefficace comme discuté plus haut | |
| 445 | + | for i := 1 to Length(s) do | |
| 446 | + | Writeln(i, ': ', ch[i]); | |
| 447 | + | ||
| 448 | + | // Ok, mais moche | |
| 449 | + | i := 1; | |
| 450 | + | for ch in s do begin | |
| 451 | + | Writeln(i, ': ', ch); | |
| 452 | + | Inc(i); | |
| 453 | + | end; | |
| 454 | + | ||
| 455 | + | // Extension proposée | |
| 456 | + | for ch in s index i do | |
| 457 | + | Writeln(i, ': ', ch); | |
| 458 | + | ||
| 459 | + | // Extension proposée pour traverser à l'envers (équivalent au downto) | |
| 460 | + | for ch in reverse s do | |
| 461 | + | Writeln(i, ': ', ch); | |
| 462 | + | ||
| 463 | + | // Avec l'extension d'index proposée | |
| 464 | + | for ch in reverse s index i do | |
| 465 | + | Writeln(i, ': ', ch); | |
| 466 | + | end. | |
| 467 | + | ``` | |
| 468 | + | ||
| 469 | + | Remarquez que l'index doit être conçu pour retourner un type arbitraire (c'est-à-dire pas forcément un entier). Par exemple, dans l'exemple d'une traversée d'arbre, l'index pourrait retourner un tableau de nœuds décrivant le chemin allant de la racine au nœud courant. | |
| 470 | + | ||
| 471 | + | ### Solution de contournement pour l'indice | |
| 472 | + | ||
| 473 | + | La propriété Current prend la forme d'un couple (Indice,Valeur), on emploie un type générique afin de pérenniser cette conception. | |
| 474 | + | ``` | |
| 475 | + | unit IndicedEnumerators; | |
| 476 | + | ||
| 477 | + | interface | |
| 478 | + | ||
| 479 | + | type | |
| 480 | + | ||
| 481 | + | generic TGIndicedEnumerator<TContainer, TElement> = class | |
| 482 | + | public type | |
| 483 | + | TIndicedValue = record | |
| 484 | + | Value: TElement; | |
| 485 | + | Ind: Integer; | |
| 486 | + | end; | |
| 487 | + | protected | |
| 488 | + | FCntnr: TContainer; | |
| 489 | + | FCurrent: TIndicedValue; | |
| 490 | + | public | |
| 491 | + | constructor Create(aCntnr: TContainer); virtual; | |
| 492 | + | function MoveNext: Boolean; virtual; abstract; | |
| 493 | + | property Current: TIndicedValue read FCurrent; | |
| 494 | + | function GetEnumerator: TGIndicedEnumerator; | |
| 495 | + | end; | |
| 496 | + | ||
| 497 | + | TCsvAnsiStringFieldEnumerator = class(specialize TGIndicedEnumerator<AnsiString, AnsiString>) | |
| 498 | + | private | |
| 499 | + | FCurrPos: Integer; | |
| 500 | + | FSeparator: Char; | |
| 501 | + | public | |
| 502 | + | constructor Create(aString: AnsiString; Separator: AnsiChar); overload; | |
| 503 | + | function MoveNext: Boolean; override; | |
| 504 | + | end; | |
| 505 | + | ||
| 506 | + | implementation | |
| 507 | + | ||
| 508 | + | { TGIndicedEnumerator } | |
| 509 | + | ||
| 510 | + | constructor TGIndicedEnumerator.Create(aCntnr: TContainer); | |
| 511 | + | begin | |
| 512 | + | FCntnr := aCntnr; | |
| 513 | + | end; | |
| 514 | + | ||
| 515 | + | function TGIndicedEnumerator.GetEnumerator: TGIndicedEnumerator; | |
| 516 | + | begin | |
| 517 | + | Result := Self; | |
| 518 | + | end; | |
| 519 | + | ||
| 520 | + | { TCsvAnsiStringFieldEnumerator } | |
| 521 | + | ||
| 522 | + | constructor TCsvAnsiStringFieldEnumerator.Create(aString: AnsiString; Separator: AnsiChar); | |
| 523 | + | begin | |
| 524 | + | inherited Create(aString); | |
| 525 | + | FCurrPos := 0; | |
| 526 | + | FSeparator := Separator; | |
| 527 | + | FCurrent.Ind := -1; | |
| 528 | + | end; | |
| 529 | + | ||
| 530 | + | function TCsvAnsiStringFieldEnumerator.MoveNext: Boolean; | |
| 531 | + | begin | |
| 532 | + | if FCurrPos > Length(FCntnr) then exit(False); | |
| 533 | + | // Premier élément | |
| 534 | + | Inc(FCurrent.ind); | |
| 535 | + | FCurrent.Value := ''; | |
| 536 | + | Inc(FCurrPos); | |
| 537 | + | while (FCurrPos <= Length(FCntnr)) and (FCntnr[FCurrPos] <> FSeparator) do | |
| 538 | + | begin | |
| 539 | + | FCurrent.Value += FCntnr[FCurrPos]; | |
| 540 | + | Inc(FCurrPos); | |
| 541 | + | end; | |
| 542 | + | Result := True; | |
| 543 | + | end; | |
| 544 | + | ||
| 545 | + | end. | |
| 546 | + | ``` | |
| 547 | + | ||
| 548 | + | Un point intéressant se trouve dans la déclaration de GetEnumerator dont le type de retour est le type énumérateur lui-même, nécessaire pour pouvoir créer un itérateur à partir de la classe elle-même, mais surtout la méthode sera correctement adaptée lors de la spécialisation et retournera alors le type spécialisé (TCsvAnsiStringFieldEnumerator dans notre exemple). Ce qui montre que la substitution lors de spécialisation ne se limite aux seules marques substitutives ("placeholders"). | |
| 549 | + | ||
| 550 | + | Dans l'exemple, la spécialisation et la dérivation sont réalisées simultanément mais ce n'est pas une nécessité, il est d'ailleurs sans doute préférable de scinder ces deux opérations et de créer un type intermédiaire spécialisé TAnsiStringEnumerator qui sans introduire la notion de séparateur permettrait de concevoir d'autres itérateurs sur des AnsiString. | |
| 551 | + | ||
| 552 | + | L'enregistrement couple (Indice,Valeur) lui-même générique sera automatiquement adapté lors de la spécialisation, cependant, son nom de type devra être qualifié à l'aide du nom de la classe d'énumérateur spécialisé comme nous pouvons le voir dans le petit programme d'exemple, afin de lever toute ambiguïté si deux types spécialisés devaient cohabiter : | |
| 553 | + | ``` | |
| 554 | + | #!/usr/bin/instantfpc | |
| 555 | + | ||
| 556 | + | // (see the Instantfpc doc) | |
| 557 | + | ||
| 558 | + | {$mode objfpc}{$H+} | |
| 559 | + | ||
| 560 | + | uses | |
| 561 | + | SysUtils, | |
| 562 | + | IndicedEnumerators; | |
| 563 | + | ||
| 564 | + | var | |
| 565 | + | IndStr: TCsvAnsiStringFieldEnumerator.TIndicedValue; | |
| 566 | + | ||
| 567 | + | begin | |
| 568 | + | // énumérateur obtenu directement à partir de la classe elle-même, la libération de l'énumérateur | |
| 569 | + | // est garantie en sortie de la boucle for .. in | |
| 570 | + | for IndStr in TCsvAnsiStringFieldEnumerator.Create(ParamStr(1), ';') do | |
| 571 | + | WriteLn(Format('%d : "%s"', [IndStr.Ind, IndStr.Value])); | |
| 572 | + | end. | |
| 573 | + | ``` | |
| 574 | + | ||
| 575 | + | Appel du script InstantFPC en ligne de commande (nom du fichier source pascal avec des droit d'exécution sous Linux + un paramètre) | |
| 576 | + | ``` | |
| 577 | + | ./TestStringFieldEnums.pas "small;string;with;separated;fields" | |
| 578 | + | ``` | |
| 579 | + | ||
| 580 | + | Cette solution a l'avantage de mettre à disposition un indice même si celui-ci n'existe pas nativement dans le type traversé, sans avoir à déclarer une variable Indice dans le code client. | |
| 581 | + | ||
| 582 | + | Enfin, l'énumérateur générique pourrait être amélioré en ajoutant aux paramètres du constructeur un indice de départ (0 devenant la valeur par défaut). | |
| 583 | + | ||
| 584 | + | # Référence | |
| 585 | + | ||
| 586 | + | * <http://17slon.com/blogs/gabr/2007/03/fun-with-enumerators.html> | |
| 587 | + | ||
| 588 | + | --- | |
| 589 | + | *From the Free Pascal wiki, page "for-in loop/fr", as the Internet Archive kept it on 2024-09-08. Copyright stays with the author who wrote it, according to the old wiki's [[Licensing]] page. If you have concerns about this material, [message the admin](/wiki-contact).* |
