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)

No comments: