December 30, 2012
Writing an iterator for a container
In my last article I explained how to write a class property being a list of integers and how to write the corresponding property editor. We wrote TIntegerList class which merely looks like the well known TStringList class except it contains integers instead of strings.
Today, we will add some code to TIntegerList class so that we can use the for..in Delphi construct. Without this construct, the code to iterate thru the list of integers and display those in a TMemo looks like this:
procedure TForm1.EnumeratorButtonClick(Sender: TObject);
var
I, N : Integer;
begin
for N := 0 to TickLine1.TickPos.Count - 1 do begin
I := TickLine1.TickPos[N];
Memo1.Lines.Add(IntToStr(I));
end;
end;
Using this for..in construct greatly simplifies the code:
procedure TForm1.EnumeratorButtonClick(Sender: TObject);
var
I : Integer;
begin
for I in TickLine1.TickPos do
Memo1.Lines.Add(IntToStr(I));
end;
For the compiler to be able to iterate thru our TIntegerList class (A "container" in Delphi terminology), we must implement a new public method GetEnumerator() function which has to return a class, a record or an interface. In my example, I will use a class.
The class must contain a function MoveNext() returning a boolean and a property Current returning the current element of the container.
We will name the enumerator class TIntegerListEnumerator. To help you better understand here is the code the compiler actually generates when you write the code above:
procedure TForm1.EnumeratorButtonClick(Sender: TObject);
var
I : Integer;
Enum : TIntegerListEnumerator;
begin
Enum := TickLine1.GetEnumerator();
try
while Enum.MoveNext do begin
I := Enum.Current;
Memo1.Lines.Add(IntToStr(I));
end;
finally
Enum.Free;
end;
end;
Remember, you don't write this code, it is generated by the compiler when you write the "for I in".
The code is quite easy to understand. The actual enumeration is done by the class TIntegerListEnumerator we have to write once. And it is trivial to write it. Here is the source code:
TIntegerListEnumerator = class
private
FIndex : Integer;
FIntegers : TIntegerList;
public
constructor Create(const AIntegers: TIntegerList);
function GetCurrent: Integer; inline;
function MoveNext: Boolean;
property Current: Integer read GetCurrent;
end;
constructor TIntegerListEnumerator.Create(const AIntegers: TIntegerList);
begin
inherited Create;
FIndex := -1;
FIntegers := AIntegers;
end;
function TIntegerListEnumerator.GetCurrent: Integer;
begin
Result := FIntegers[FIndex];
end;
function TIntegerListEnumerator.MoveNext: Boolean;
begin
Result := FIndex < (FIntegers.Count - 1);
if Result then
Inc(FIndex);
end;
The class has two variables: FIndex and FIntegers. FIndex will be used to iterate thru all values and FIntegers will reference the values to be iterated.
The construction takes one argument which is the integer list to iterate. It initializes the FIndex variable to -1 which is the starting value since it gets incremented before each iteration by MoveNext.
The procedure MoveNext has two responsibilities: increment the iteration variable and return the status of the iteration. If MoveNext returns TRUE, then it means there are more items in the list.
The property Current, implemented using the setter GetCurrent simply returns the integer whose index is given by the iteration variable FIndex.
That all we need for the iteration class!
The actual container class, TIntegerList need only one public method: GetEnumerator. The code is really simple:
function TIntegerList.GetEnumerator: TIntegerListEnumerator;
begin
Result := TIntegerListEnumerator.Create(Self);
end;
That's it !
Note: Have a look at Internet Component Suite (ICS)
Labels:
delphi,
embarcadero,
iterator,
XE3
Subscribe to:
Post Comments (Atom)
No comments:
Post a Comment