Delphi.cz

Český portál Delphi

Enumerátory - dar z nebes

Jedním z důvodů proč už jsem opustil nadobro staré verze Delphi (pod D2007) jsou enumerátory. Toto rozšíření (resp. standard všech moderních jazyků) je pro mne tak významné, že není cesty zpět. Samozřejmě ve spojení s generiky je to teprve jízda, ale i bez nich to jednomu člověku usnadní práci a jsem rád, že i významné komponenty toto podporují. Na konci článku přímo jeden enumerátor vytvoříme.

Na úvod několik ukázek, nejjednodušší ukázka je např. pro práci s řetězci:

var
  c:char;
begin
  for c in sText do
  begin
  //
  end;
end;

TStringList

var
  list:TStringList;
  s: string;
begin
  list :=TStringList.Create;
  list.CommaText := 'r,a,d';
  for s in list do
    ListBox1.Items.Add(s);
  list.Free;
end;

Nebo VirtualTreeView jako ukázka komponent třetí strany - no není to paráda:

var
  pNode: PVirtualNode;
begin
   for pNode in VirtualStringTree1.CheckedNodes do
   begin
//     ..
   end;
end;

S generiky

var
  list:TList<string>;
  s: string;
begin
  list :=TList<string>.Create;
  list.Add('prvni');
  list.Add('druhy');

  for s in list do
    ListBox1.Items.Add(s);
  list.Free;
end;

Někdy nastane ta chvíle, kdy vytvoříte vlastní třídu a chcete přidat enumerátor. Postup ukáži na ukázce. Mějme třídu, která obsahuje string a enumerátor bude vracet pro jednoduchost jednotlivé znaky (tj. komplikovaná varianta prvního příkladu).

  TDemoString = class
  protected
    FText: string;
  public
    constructor Create(const AText:string);
    function GetEnumerator: TDemoEnumerator;
  end;

constructor TDemoString.Create(const AText: string);
begin
  FText := AText;
end;

function TDemoString.GetEnumerator: TDemoEnumerator;
begin
  Result := TDemoEnumerator.Create(Self);
end;

Klíčové je GetEnumerator, což je metoda, která vrátí instanci enumerátoru. Ideálně by měla třída implementovat rozhraní IEnumerable, ale není to nutné, stačí public metoda GetEnumerator.

Nyní vytvoříme enumerátor, v souboru je před deklarací třídy, protože třída se na něho odkazuje.

type
  TDemoString = class;

  TDemoEnumerator = record
  private
    FIndex: Integer;
    FDemo : TDemoString;
  public
    constructor Create(demo: TDemoString);
    function GetCurrent: Char; inline;
    function MoveNext: Boolean;
    property Current: Char read GetCurrent;

  end;

{ TDemoEnumerator }

constructor TDemoEnumerator.Create(demo: TDemoString);
begin
  FDemo := demo;
  FIndex := 0;
end;

function TDemoEnumerator.GetCurrent: Char;
begin
  Result := FDemo.FText[FIndex];
end;

function TDemoEnumerator.MoveNext: Boolean;
begin
  Result := FIndex < Length(FDemo.FText);
  if Result then
    Inc(FIndex);
end;

Enumerátor musí implementovat nejméně MoveNext, GetCurrent. První metoda se posouvá na další záznam, druhá vrací aktuální záznam. Myslím, že implementace mého enumerátoru je velmi přímočará.

Poslední věc, spíše zajímavost je následující fígl: Uvedený VirtualTreeView má enumerátor deklarován následovně.

  TVTVirtualNodeEnumerator = {$ifdef COMPILER_10_UP}record{$else}class{$endif}
  private

Předpokládám, že je to z hlediska optimalizace, jelikož záznamy jsou vytvářeny na zásobníku, resp. třída na haldě, tj. záznamy budou efektivnější - není třeba alokace.

Rád si přečtu Vaše připomínky a vylepšení.

Datum: 2014-11-10 21:48:00 Tagy: kompilátor

Delphi Praxe