The For..in..do statement

[next] [prev] [prev-tail] [tail] [up]

#### 13.2.5 The For..in..do statement

As of version 2.4.2, Free Pascal supports the For..in loop construction. A for..in loop is used in case one wants to calculate something a fixed number of times with an enumerable loop variable. The prototype syntax is as follows:

_________________________________________________________________________________________________________
For statement
___________________________________________________________________

Here, Statement can be a compound statement. The enumerable must be an expression that consists of a fixed number of elements: the loop variable will be made equal to each of the elements in turn and the statement following the do keyword will be executed.

The enumerable expression can be one of five cases:

1. An enumeration type identifier. The loop will then be over all elements of the enumeration type. The control variable must be of the enumeration type.
2. A set value. The loop will then be over all elements in the set, the control variable must be of the base type of the set.
3. An array value. The loop will be over all elements in the array, and the control variable must have the same type as an element in the array. As a special case, a string is regarded as an array of characters.
4. An enumerable class, object, or extended record instance. This is an instance of any structured type that supports the IEnumerator and IEnumerable interfaces. In this case, the control variable’s type must equal the type of the IEnumerator.GetCurrent return value.
5. Any type for which an enumerator operator is defined. The enumerator operator must return a structured type that implements the IEnumerator interface. The type of the control variable’s type must equal the type of the enumerator’s GetCurrent return value type.

The simplest case of the for..in loop is using an enumerated type:

Type
TWeekDay = (monday, tuesday, wednesday, thursday,
friday,saturday,sunday);
Var
d : TWeekday;
begin
for d in TWeekday do
writeln(d);
end.

This will print all week days to the screen.

The above for..in construct is equivalent to the following for..to construct:

Type
TWeekDay = (monday, tuesday, wednesday, thursday,
friday,saturday,sunday);
Var
d : TWeekday;
begin
for d:=Low(TWeekday) to High(TWeekday) do
writeln(d);
end.

A second case of for..in loop is when the enumerable expression is a set, and then the loop will be executed once for each element in the set:

Type
TWeekDay = (monday, tuesday, wednesday, thursday,
friday,saturday,sunday);
Var
Week : set of TWeekDay
= [monday, tuesday, wednesday, thursday, friday];
d : TWeekday;
begin
for d in Week do
writeln(d);
end.

This will print the names of the week days to the screen. Note that the variable d is of the same type as the base type of the set.

The above for..in construct is equivalent to the following for..to construct:

Type
TWeekDay = (monday, tuesday, wednesday, thursday,
friday,saturday,sunday);
Var
Week : set of TWeekDay
= [monday, tuesday, wednesday, thursday, friday];
d : TWeekday;
begin
for d:=Low(TWeekday) to High(TWeekday) do
if d in Week then
writeln(d);
end.

The third possibility for a for..in loop is when the enumerable expression is an array:

var
a : Array[1..7] of string
= ('monday','tuesday','wednesday','thursday',
'friday','saturday','sunday');
Var
S : String;
begin
For s in a do
Writeln(s);
end.

This will also print all days in the week, and is equivalent to

var
a : Array[1..7] of string
= ('monday','tuesday','wednesday','thursday',
'friday','saturday','sunday');
Var
i : integer;
begin
for i:=Low(a) to high(a) do
Writeln(a[i]);
end.

A string type is equivalent to an array of char, and therefore a string can be used in a for..in loop. The following will print all letters in the alphabet, each letter on a line:

Var
c : char;
begin
for c in 'abcdefghijklmnopqrstuvwxyz' do
writeln(c);
end.

Note that multi-dimensional arrays are also supported:

uses
SysUtils;
type
TTestStringArray = array[0..10] of String;
Var
TwoD : array[0..3] of TTestStringArray;
var
i,j : integer;
S : String;
begin
for i:=0 to 3 do
for j:=0 to 10 do
TwoD[i,J]:=Format('%.2dx%.2d',[i,j]);
for S in twod do
Writeln(S);
end.

This will loop over all dimensions from left to right.

The fourth possibility for a for..in loop is using classes. A class can implement the IEnumerable interface, which is defined as follows:

IEnumerable = interface(IInterface)
function GetEnumerator: IEnumerator;
end;

The actual return type of the GetEnumerator must not necessarily be an IEnumerator interface, instead, it can be a class which implements the methods of IEnumerator:

IEnumerator = interface(IInterface)
function GetCurrent: TObject;
function MoveNext: Boolean;
procedure Reset;
property Current: TObject read GetCurrent;
end;

The Current property and the MoveNext method must be present in the class returned by the GetEnumerator method. The actual type of the Current property need not be a TObject. When encountering a for..in loop with a class instance as the “in” operand, the compiler will check each of the following conditions:

- Whether the class in the enumerable expression implements a method GetEnumerator
- Whether the result of GetEnumerator is a class with the following method:
  Function MoveNext : Boolean

- Whether the result of GetEnumerator is a class with the following read-only property:
  Property Current : AType;

  The type of the property must match the type of the control variable of the for..in loop.

Neither the IEnumerator nor the IEnumerable interfaces must actually be declared by the enumerable class: the compiler will detect whether these interfaces are present using the above checks. The interfaces are only defined for Delphi compatibility and are not used internally. (it would also be impossible to enforce their correctness).

The Classes unit contains a number of classes that are enumerable:

TFPList
  Enumerates all pointers in the list.
TList
  Enumerates all pointers in the list.
TCollection
  Enumerates all items in the collection.
TStringList
  Enumerates all strings in the list.
TComponent
  Enumerates all child components owned by the component.

Thus, the following code will also print all days in the week:

{$mode objfpc}
uses classes;
Var
Days : TStrings;
D : String;
begin
Days:=TStringList.Create;
try
Days.Add('Monday');
Days.Add('Tuesday');
Days.Add('Wednesday');
Days.Add('Thursday');
Days.Add('Friday');
Days.Add('Saturday');
Days.Add('Sunday');
For D in Days do
Writeln(D);
Finally
Days.Free;
end;
end.

Note that the compiler enforces type safety: declaring D as an integer will result in a compiler error:

testsl.pp(20,9) Error: Incompatible types: got "AnsiString" expected "LongInt"

The above code is equivalent to the following:

{$mode objfpc}
uses classes;
Var
Days : TStrings;
D : String;
E : TStringsEnumerator;
begin
Days:=TStringList.Create;
try
Days.Add('Monday');
Days.Add('Tuesday');
Days.Add('Wednesday');
Days.Add('Thursday');
Days.Add('Friday');
Days.Add('Saturday');
Days.Add('Sunday');
E:=Days.getEnumerator;
try
While E.MoveNext do
begin
D:=E.Current;
Writeln(D);
end;
Finally
E.Free;
end;
Finally
Days.Free;
end;
end.

Both programs will output the same result.

The fifth and last possibility to use a for..in loop can be used to enumerate almost any type, using the enumerator operator. The enumerator operator must return a class that has the same signature as the IEnumerator approach above. The following code will define an enumerator for the Integer type:

Type
TEvenEnumerator = Class
FCurrent : Integer;
FMax : Integer;
Function MoveNext : Boolean;
Property Current : Integer Read FCurrent;
end;
Function TEvenEnumerator.MoveNext : Boolean;
begin
FCurrent:=FCurrent+2;
Result:=FCurrent<=FMax;
end;
operator enumerator(i : integer) : TEvenEnumerator;
begin
Result:=TEvenEnumerator.Create;
Result.FMax:=i;
end;
var
I : Integer;
m : Integer = 4;
begin
For I in M do
Writeln(i);
end.

The loop will print all nonzero even numbers smaller or equal to the enumerable. (2 and 4 in the case of the example).

Care must be taken when defining enumerator operators: the compiler will find and use the first available enumerator operator for the enumerable expression. For classes this also means that the GetEnumerator method is not even considered. The following code will define an enumerator operator which extracts the object from a stringlist:

{$mode objfpc}
uses classes;
Type
TDayObject = Class
DayOfWeek : Integer;
Constructor Create(ADayOfWeek : Integer);
end;
TObjectEnumerator = Class
FList : TStrings;
FIndex : Integer;
Function GetCurrent : TDayObject;
Function MoveNext: boolean;
Property Current : TDayObject Read GetCurrent;
end;
Constructor TDayObject.Create(ADayOfWeek : Integer);
begin
DayOfWeek:=ADayOfWeek;
end;
Function TObjectEnumerator.GetCurrent : TDayObject;
begin
Result:=FList.Objects[Findex] as TDayObject;
end;
Function TObjectEnumerator.MoveNext: boolean;
begin
Inc(FIndex);
Result:=(FIndex<FList.Count);
end;
operator enumerator (s : TStrings) : TObjectEnumerator;
begin
Result:=TObjectEnumerator.Create;
Result.Flist:=S;
Result.FIndex:=-1;
end;
Var
Days : TStrings;
D : String;
O : TdayObject;
begin
Days:=TStringList.Create;
try
Days.AddObject('Monday',TDayObject.Create(1));
Days.AddObject('Tuesday',TDayObject.Create(2));
Days.AddObject('Wednesday',TDayObject.Create(3));
Days.AddObject('Thursday',TDayObject.Create(4));
Days.AddObject('Friday',TDayObject.Create(5));
Days.AddObject('Saturday',TDayObject.Create(6));
Days.AddObject('Sunday',TDayObject.Create(7));
For O in Days do
Writeln(O.DayOfWeek);
Finally
Days.Free;
end;
end.

The above code will print the day of the week for each day in the week.

If a class is not enumerable, the compiler will report an error when it is encountered in a for...in loop.

Remark Like the for..to loop, it is not allowed to change (i. e. assign a value to) the value of a loop control variable inside the loop.

[next] [prev] [prev-tail] [front] [up]