- 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;
- 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;
- 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.
- 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;
- 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.
- 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;