for-in loop
"for-in" loop exists in delphi starting from 2005 version.
Delphi implementation
It has the next syntax:
String loop
<delphi> procedure StringLoop(S: String); var
C: Char;
begin
for C in S do DoSomething(C);
end; </delphi>
Array loop
<delphi> procedure ArrayLoop(A: Array of Byte); var
B: Byte;
begin
for B in A do DoSomething(B);
end; </delphi>
Set loop
<delphi> 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; </delphi>
Traversing container
To traverse some container class you need to add an enumerator for it. Enumerator is a class built by the next template:
<delphi> TSomeEnumerator = class public
function MoveNext: Boolean; property Current: TSomeType;
end; </delphi>
There are only 2 things required for the enumerator: MoveNext method which asks enumerator to step forward and property Current which can return any desired type.
Next thing is to add magic GetEnumerator method to the container class which returns an enumerator instance.
For example: <delphi> 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
// some logic to get the next node from a tree if FCurrent = nil then FCurrent := FTree.GetFirstNode else FCurrent := FTree.GetNextNode(FCurrent); Result := FCurrent <> FTree.GetLastNode;
end;
function TEnumerableTree.GetEnumerator: TTreeEnumerator; begin
Result := TTreeEnumerator.Create(Self);
end;
</delphi>
After this you are able to execute the next code:
<delphi> procedure TreeLoop(ATree: TEnumerableTree); var
ANode: TNode;
begin
for ANode in ATree do DoSomething(ANode);
end; </delphi>
Ofcource enumerator support is builtin to the basic classes: TList, TStrings, TCollection, TComponent, ...
For-in loop can be easily translated into the while loop. Two next examples doing same things:
Example 1. <delphi> procedure TraverseStrings(AStrings: TStrings); var
S: String;
begin
for S in AStrings do DoSomething(S);
end; </delphi>
Example 2. <delphi> procedure TraverseStrings(AStrings: TStrings); var
S: String; Enumerator: TStringsEnumerator;
begin
Enumerator := AStrings.GetEnumerator; while Enumerator.MoveNext do DoSomething(Enumerator.Current);
end; </delphi>
It is also possible to make some class enumerable if you implement the next interface for the container: <delphi>
IEnumerable = interface(IInterface) function GetEnumerator: IEnumerator; end;
</delphi>
Where IEnumerator is declared as: <delphi>
IEnumerator = interface(IInterface) function GetCurrent: TObject; function MoveNext: Boolean; procedure Reset; property Current: TObject read GetCurrent; end;
</delphi>
Limitations
1. It is imposible to traverse a collection:
The next code is not valid: <delphi> type
TColor = (clRed, clBlue, clBlack);
var
Color: TColor;
begin
for Color in TColor do DoSomething(Color);
end. </delphi>
Although you can traverse a set. So the next code is valid: <delphi> type
TColor = (clRed, clBlue, clBlack);
var
Color: TColor;
begin
for Color in [clRed..clBlack] do DoSomething(Color);
end. </delphi>
2. It is imposible to choose among different possible enumerators. For example you can traverse a tree using different routs. The well known algorithms are: preorder, postorder, inorder and breadth‑first traversals. Therefore it would be useful have an ability to choose enumerator. For example using the next syntax:
<delphi> type
TTreeEnumeratorType = (tePreOrder, tePostOrder, teInOrder, teBreadthFirst)
var
Node: TNode;
begin
for Node in Tree using GetEnumerator(teInOrder) do // or 'using GetInOrderEnumerator' or ... Dosomething(Node);
end. </delphi>