How to do it...

  1. We have to create an enumerable function result. The function simply returns the actual enumerable type. This type is not freed automatically by the compiler, so you've got to use a value type or an interfaced type. For the sake of simplicity, let's code to return a record type:
function EachRows(const AFileName: String): TFileEnumerable; 
begin 
  Result := TFileEnumerable.Create(AFileName); 
end; 
  1. The TFileEnumerable type is defined as follows:
type 
  TFileEnumerable = record 
  private 
    FFileName: string; 
  public 
    constructor Create(AFileName: String); 
    function GetEnumerator: TEnumerator<String>; 
  end; 
. . . 
constructor TFileEnumerable.Create(AFileName: String); 
begin 
  FFileName := AFileName; 
end; 
 
function TFileEnumerable.GetEnumerator: TEnumerator<String>; 
begin 
  Result := TFileEnumerator.Create(FFileName); 
end; 
  1. This record is required only because you need a type that has a GetEnumerator method defined. This method is called automatically by the compiler when the type is used on the right side of the for...in loop.
  2. The TFileEnumerator type is the actual enumerator, and is declared in the implementation section of the unit. Remember, this object is automatically freed by the compiler, because it is the return of the GetEnumerator call:
type 
  TFileEnumerator = class(TEnumerator<String>) 
  private 
    FCurrent: String; 
    FFile: TStreamReader; 
  protected 
    constructor Create(AFileName: String); 
    destructor Destroy; override; 
    function DoGetCurrent: String; override; 
    function DoMoveNext: Boolean; override; 
  end; 
 
{ TFileEnumerator } 
 
constructor TFileEnumerator.Create(AFileName: String); 
begin 
  inherited Create; 
  FFile := TFile.OpenText(AFileName); 
end; 
 
destructor TFileEnumerator.Destroy; 
begin 
  FFile.Free; 
  inherited; 
end; 
 
function TFileEnumerator.DoGetCurrent: String; 
begin 
  Result := FCurrent; 
end; 
 
function TFileEnumerator.DoMoveNext: Boolean; 
begin 
  Result := not FFile.EndOfStream; 
  if Result then 
    FCurrent := FFile.ReadLine; 
end; 
  1. The enumerator inherits from TEnumerator<String> because each row of the file is represented as a string. This class also gives a mechanism to implement the required methods. The DoGetCurrent method (called internally by the TEnumerator<T>.GetCurrent method) returns the current line. The DoMoveNext method (called internally by the TEnumerator<T>.MoveNext method) returns true or false, depending on whether there are more lines to read in the file or not. Remember that this method is called before the first call to the GetCurrent method. After the first call to the DoMoveNext method, FCurrent is properly set to the first row of the file.
  2. The compiler generates a piece of code similar to the following pseudo-code:
it = typetoenumerate.GetEnumerator; 
while it.MoveNext do 
begin 
  S := it.Current; 
  //do something useful with string S 
end 
it.free;