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