Jak se tak potuluji po zákaznících se školením nebo konzultacemi, tak narážím na opakovanou situaci s FieldByName (případně podobnými funkcemi).
Odbočka: Nemám rád datamoduly plné různých SQL příkazů a datasetů (popravdě mne přímo děsí), jsem zastáncem dynamických SQL a datasetů vytvořených až je třeba. Důvodem je pro mne lepší modularita, lepší udržovatelnost, nahraditelnost a vůbec samá pozitiva a sociální jistoty.
Na druhou stranu pokud tedy už do datamodulu vložíte nějaký dataset, můžete definovat jeho fieldy a Delphi vytvoří objekty (resp. komponenty) reprezentující jednotlivé fieldy, jako např. TStringField, TIntegerField atd. Všechny jsou následníkem TField.
Myslím si, že původní návrh Delphi předpokládal, že programátoři budou v podstatě používat pouze tyto vizuální objekty a tak rychle přistupovat k datům, přes property daného objektu. Jenže z hlediska udržovatelnosti, čitelnosti atd. se hodně často místo toho používá dynamické nalezení TField v datasetu podle jména přes FieldByName.
var
ds: TDataSet;
x : Integer;
begin
// ds je naplněn daty
x := 0;
while not ds.eof do
begin
x := x + ds.FieldByName('ParamA').AsInteger + ds.FieldByName('ParamB').AsInteger;
ds.Next;
end;
end;
Věřím, že je to celkem klasická konstrukce a funguje uspokojivě, data se sečtou atd.
Co je špatně, resp. co se dá optimalizovat? Samozřejmě zrušit zbytečné akce, které se během cyklu nemění (invariantu cyklu). FieldByName(xx) vrátí pořád stejného následníka TField během všech iterací, proto je zbytečné ho pořád zjišťovat.
var
ds: TDataSet;
x : Integer;
oFieldParamA, oFieldParamB: TField;
begin
// ds je naplněn daty
x := 0;
oFieldParamA := ds.FieldByName('ParamA');
oFieldParamB := ds.FieldByName('ParamB');
while not ds.eof do
begin
x := x + oFieldParamA.AsInteger + oFieldParamB.AsInteger;
ds.Next;
end;
end;
Program se nám trochu nepatrně prodloužil a vzhledem k zvýrazňování textu i krapánek možná více znepřehlednil, ale výsledek je velmi výrazně efektivnější.
Pozorný čtenář jasně chápe jak moc ušetříme, ale přesto: oFieldParamA.AsInteger je v podstatě jen odkaz na metodu objektu (v ideálním případě jen pár instrukcí), kdežto FieldByName znamená hledání podle jména v seznamu stringu (interně je volán FindField) a teprve pak to zavolání metody objektu. Konkrétně pro zajímavost např.
Delphi 5
function TFields.FindField(const FieldName: string): TField;
var
I: Integer;
begin
for I := 0 to FList.Count - 1 do
begin
Result := FList.Items[I];
if AnsiCompareText(Result.FFieldName, FieldName) = 0 then Exit;
end;
Result := nil;
end;
Jednoduché prohledání…
Delphi XE
function TFields.FindField(const FieldName: string): TField;
var
I: Integer;
HashValue: Cardinal;
begin
if FList.Count > 0 then
begin
HashValue := TNamedItem.HashName(FieldName);
for I := 0 to FList.Count - 1 do
begin
Result := FList.Items[I];
if (Result.FFieldNameHashValue = HashValue) and
(AnsiCompareText(Result.FFieldName, FieldName) = 0) then
Exit;
end;
end;
Result := nil;
end;
Prohledání přes hash, ale všimněte si, že po nalezení hashe je provedeno ještě prověření.
Aktuální Delphi
function TFields.FindField(const FieldName: string): TField;
begin
FDict.TryGetValue(AnsiLowerCase(FieldName), Result);
end;
kde FDict: TDictionary<string, TField>, ale pozor: AnsiLowerCase taky něco stojí.
Je vidět, že autoři Delphi jsou si vědomi toho, co se používá a snaží se i neoptimální kód optimalizovat přímo v RTL.
Naše "optimalizace" bude tím efektivnější, čím bude požadovaný field dále v seznamu fieldů, resp. čím bude seznam fieldů rozsáhlejší a hlavně čím bude větší počet řádků v datasetu, protože už při dvou záznamech v uvedeném případě urychlíme původní program cca o polovinu (to co by se provádělo 2x se provede jen zhruba 1x, všechno ostatní je proti FieldByName zanedbatelné - no možná až na Next).
Poznámky klidně napište do komentářů.