It is not a surprise that most programming languages in our time have the built-in support for enumerators/iterators. This is a mandatory feature since enumerators and enumerable collections simplify the development of applications and make the code cleaner. Languages such as C#, Java or Delphi have built-in syntax constructs that allow simplified and clean ways of enumerating over a collection by the means of “foreach” or special “for” loops.
In Delphi, enumerators have been added to the language for some time now in form of a special “for” loop (you can read in the Help about it more). It allows enumerating elements of an array, string and in most collection classes. While in the case of intrinsic types, the compiler simply transforms the loop into an ordinary “for” loop, in the case of collection classes things become more interesting. But let’s take each case separately:
Enumerating over a String:
var LString: String; LChar: Char; begin LString := 'Hello World'; for LChar in LString do Write(c); end;
It’s simple as that! It does not have such a negative impact on the speed and make your code look much cleaner, it also helps avoid using an index variable. The same method can be applied for AnsiString, WideString and ShortString.
Enumerating over an Array:
var LArr: TBytes; LByte: Byte; begin LArr := TBytes.Create(1, 2, 3, 4, 5); for LByte in LArr do Write(LByte); end.
Looks simple. And indeed it is! There are more use cases for different types of arrays, but you can find that in the Help.
Enumerating over a collection:
var List: TList<Integer>; I: Integer; begin List := TList<Integer>.Create(); List.AddRange([1, 2, 3, 4, 5]); for I in List do WriteLn(I); end.
In this case the overhead for the “for” loop is higher since a call to List.GetEnumerator() is made to obtain an enumerator object. Then at each iteration the MoveNext() and Current() methods are called on the enumerator to move to the next element in the list and retrieve its value.
There are in fact only a few rules that you must abide in order to support enumeration in your collections:
- You must have either a class, interface or record type.
- Your class, interface or record must expose a GetEnumerator() function that returns an enumerator.
- The enumerator can be either a class, interface or a record type.
- The enumerator must expose a MoveNext() function that returns a Boolean and a Current property that returns the current element.
- When the enumerator is created there is no current element selected. Only after the first call to MoveNext() your enumerator must select the first element in the collection.
- MoveNext() must return true if the next element was selected or false if the collection is finished.
Extreme case — enumerating over a record using a record enumerator:
type { The enumerator record } TRecordEnumerator = record private FArray: TBytes; FIndex: Integer; function GetCurrent: Byte; public function MoveNext(): Boolean; property Current: Byte read GetCurrent; end; { The record/collection that will be enumerated } TRecordCollection = record private FArray: TBytes; public function GetEnumerator(): TRecordEnumerator; end; { TRecordCollection } function TRecordCollection.GetEnumerator: TRecordEnumerator; begin Result.FArray := FArray; Result.FIndex := -1; end; { TRecordEnumerator } function TRecordEnumerator.GetCurrent: Byte; begin Result := FArray[FIndex]; end; function TRecordEnumerator.MoveNext: Boolean; begin Inc(FIndex); if FIndex >= Length(FArray) then Exit(false); Exit(true); end; var LColl: TRecordCollection; B: Byte; begin LColl.FArray := TBytes.Create(1, 2, 3, 4, 5, 6); for B in LColl do WriteLn(B); ReadLn; end.
The compiler doesn’t really care what it is enumerating and what it uses to do that. It simply must find the required methods exposed in the enumerated collection and in the enumerator.