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.