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 in statement

--            -    -            -   -         -   -         ----
  for-in-statement  for  control- variable in  enumerable  do  statement

--            -              -----------------------------------
  control- variable variable-identifier

--         ---                    ------------------------------
  enumerable  -enumerated- type-identifier|
                    expression
___________________________________________________________________

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:

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.