søndag den 30. oktober 2011

"For in"-operatoren i Delphi 2007

"For in"-operatoren blev introduceret i Delphi 2007, tror jeg nok. Den var der med sikkerhed ikke i Delphi 7, og jeg må indrømme at jeg ikke rigtigt har brugt nogle af de mellemliggende Delphi versioner. De var efter min smag ikke stabile nok, og Delphi 8 fandt jeg aldrig rigtigt ud af, hvad meningen var med.

Men pyt med det! Måske nogle af mine læsere ved bedre end mig, og kan oplyse mig om hvorvidt "For in"-operatoren fandtes i Delphi 2005 og eller 2006?

Først vil jeg lige komme med en opfriskning af, hvordan operatoren ser ud i Delphi:

var
  s : String;
begin
  for s in Memo1.Lines do
    Memo2.Lines.Add(s);
end;

Et lidt tænkt eksempel, men dog et eksempel :o) Jeg antager de fleste er bekendt med den ovenstående syntaks, og således vil jeg ikke gå i yderligere detaljer med dens betydning. Derimod vil jeg her vise, hvordan man selv kan implementere operatoren på sine egne klasser. Typisk lister.

Skal man have en typestærk liste i Delphi 2007, er man nød til selv at lave en nedarvning fra TObjectList, i stil med den her:

unit MyListU;

interface

uses
  Contnrs;

type
  TMyClass = class
  private
    FSomeString: String;
    FSomeInteger: Integer;
    procedure SetSomeInteger(const Value: Integer);
    procedure SetSomeString(const Value: String);
  public
    property SomeInteger : Integer read FSomeInteger write SetSomeInteger;
    property SomeString : String read FSomeString write SetSomeString;
  end;

  TMyList = class(TObjectList)
  private
    function GetItem(Index: Integer): TMyClass;
    procedure SetItem(Index: Integer; const Value: TMyClass);
  public
    property Items[Index: Integer]: TMyClass read GetItem write SetItem; default;
  end;

implementation

{ TMyClass }

procedure TMyClass.SetSomeInteger(const Value: Integer);
begin
  FSomeInteger := Value;
end;

procedure TMyClass.SetSomeString(const Value: String);
begin
  FSomeString := Value;
end;

{ TMyList }

function TMyList.GetItem(Index: Integer): TMyClass;
begin
  Result := inherited GetItem(Index) as TMyClass;
end;

procedure TMyList.SetItem(Index: Integer; const Value: TMyClass);
begin
  inherited SetItem(Index, Value);
end;

end.

Når så man vil bruge sin liste og vil iterere sig i gennem den, kan man fx. gøre det sådan her:

procedure TMainForm.BtnTest1Click(Sender: TObject);
var
  MyList : TMylist;
  MyObject : TMyClass;
  i : Integer;
begin
  MyList := TMylist.Create;
  //Todo: Tilføj elementer

  for I := 0 to MyList.Count - 1 do
    begin
      MyObject := MyList[i];
      //Todo: brug MyObject
    end;

  FreeAndNil(MyList);
end;

Der findes også en anden mulighed: At implementere sin egen "for in"-operator. Jeg tager udgangspunkt i min liste fra før, og har nu lavet nogle tilføjelser, som er dokumenteret i koden.

unit MyListU;

interface

uses
  Contnrs;

type
  {
    Først skal man lave et par Forward declarations, for at få det hele til at
    compilere.
  }
  TMyClass = class;
  TMyList = class;

  {
    Så skal du bugge en klasse op over den nedenstående skabelon.
    Det er en helt triviel øvelse som vist ikke kræver det store :o)
  }
  TMyListEnumerator = class
  private
    FIndex: Integer;
    FList: TMyList;
  public
    constructor Create(AList: TMyList);
    function GetCurrent: TMyClass;
    function MoveNext: Boolean;
    property Current: TMyClass read GetCurrent;
  end;

  TMyClass = class
  private
    FSomeString: String;
    FSomeInteger: Integer;
    procedure SetSomeInteger(const Value: Integer);
    procedure SetSomeString(const Value: String);
  public
    property SomeInteger : Integer read FSomeInteger write SetSomeInteger;
    property SomeString : String read FSomeString write SetSomeString;
  end;

  TMyList = class(TObjectList)
  private
    function GetItem(Index: Integer): TMyClass;
    procedure SetItem(Index: Integer; const Value: TMyClass);
  public
    {
      GetEnumerator er en metode som Delphi kalder når man itererer sig igennem
      listen, med en "for in"-operator.
    }
    function GetEnumerator: TMyListEnumerator;
    property Items[Index: Integer]: TMyClass read GetItem write SetItem; default;
  end;

implementation

{ TMyClass }

procedure TMyClass.SetSomeInteger(const Value: Integer);
begin
  FSomeInteger := Value;
end;

procedure TMyClass.SetSomeString(const Value: String);
begin
  FSomeString := Value;
end;

{ TMyList }

function TMyList.GetEnumerator: TMyListEnumerator;
begin
  //Der skal bare retuneres en ny instans. Delphi nedlægger den selv efter brug.
  Result := TMyListEnumerator.Create(Self);
end;

function TMyList.GetItem(Index: Integer): TMyClass;
begin
  Result := inherited GetItem(Index) as TMyClass;
end;

procedure TMyList.SetItem(Index: Integer; const Value: TMyClass);
begin
  inherited SetItem(Index, Value);
end;

{ TMyListEnumerator }

constructor TMyListEnumerator.Create(AList: TMyList);
begin
  inherited Create;
  FIndex := -1;
  FList := AList;
end;

function TMyListEnumerator.GetCurrent: TMyClass;
begin
  Result := FList[FIndex];
end;

function TMyListEnumerator.MoveNext: Boolean;
begin
  Result := FIndex < FList.Count - 1;
  if Result then
    Inc(FIndex);
end;

end.


Således er jeg nu klar til at bruge den operator jeg lige har implementeret:

procedure TMainForm.BtnTest2Click(Sender: TObject);
var
  MyList : TMylist;
  MyObject : TMyClass;
begin
  MyList := TMylist.Create;
  //Todo: Tilføj elementer

  for MyObject in MyList do
    begin
      //Todo : brug MyObject
    end;

  FreeAndNil(MyList);
end;



Jens Borrisholt

2 kommentarer:

  1. Hej Jens
    Fint Blog indlæg.
    For eksemplets skyld har du fuldstændig ret. Dette er en måde at implementere en typestærk liste.
    Givet de Delphi versioner vi benytter i dag (jeg kan heller ikke huske hvornår tingene kom til... Men måske Delphi 2010), ville jeg nok vælge en generics liste som myList : Tobjectlist;

    Men så får man ikke lejlighed til at implementere sin egen for in ;)

    Men tak for indlægget.

    Jens Fudge

    SvarSlet
  2. Hej Jens

    Både ja og nej. Du har fuldstændig ret i Delphi 2010 og senere vil man klart vælge en Generist liste og dermed vil der blive meget mindre kode.

    Men den der med at du ikke får lov til at lave din gene operator er ikke helt korrekt, hvis ud vil have mulighed for flere operatorer skal man selv implementere dem.

    Men tak for indput. Så fik jag da lige en ide til en post mere :o)

    SvarSlet