V době vydání Delphi 2009 jsem popisoval Anonymní metody (v jiných jazycích někdy jako closure), ale nějak mi nedošlo to hlavní, resp. nějak jsem nepochopil důležitost jedné vlastnosti a to je "capture", tj. zachycení stavu lokálních proměnných čímž se zásadně odlišují od typu funkce nebo metoda.
Mějme následující kód u formuláře. Máme dvě anonymní, které mají stejné parametry, ale jedna je deklarována jako nový typ, druhá používá generickou deklaraci anonymní metody ze sysutils (to aby bylo patrno, že generika nejsou jen kolekce a listy).
// SysUtils: TProc<T1,T2> = reference to procedure (Arg1: T1; Arg2: T2);
Takže na začátku ve formcreate jsou obě reference vyprázdněny, jedno tlačítko je nastaví, druhé je zavolá.
type
TRefIntProc = reference to procedure (const sVal:string; iAdd: Integer);
TForm2 = class(TForm)
Button1: TButton;
Button2: TButton;
procedure Button1Click(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure Button2Click(Sender: TObject);
private
{ Private declarations }
FIntProc: TRefIntProc;
FIntProc2: TProc<string, Integer>;
public
{ Public declarations }
end;
var
Form2: TForm2;
implementation
{$R *.dfm}
procedure TForm2.Button1Click(Sender: TObject);
var
i: integer;
begin
i := 3;
// nastav první anonymní metodu
FIntProc := procedure (const sVal:string; iAdd: Integer)
begin
inc(i, iAdd);
ShowMessage(sVal+' IntProc:'+IntToStr(i));
end;
// a druhou
FIntProc2 := procedure (sVal:string; iAdd: Integer)
begin
inc(i, iAdd);
ShowMessage(sVal+' IntProc2:'+IntToStr(i));
end;
inc(i); //4
end;
procedure TForm2.Button2Click(Sender: TObject);
begin
if assigned(FIntProc) then
FIntProc('Button2Click', 4);
if assigned(FIntProc2) then
FIntProc2('Button2Click', 3);
end;
procedure TForm2.FormCreate(Sender: TObject);
begin
FIntProc := nil;
FIntProc2 := nil;
end;
Všimněte si, prosím, v obsluze Button1Click práce s proměnnou i, pro jistotu i = 4 na konci obsluhy. Anonymní metody jsou přiřazeny v okamžiku kdy i = 3.
V obsluze Button2Click je provedeno volání obou uložených metod (přes assigned, kdyby to někoho napadlo volat před Button1). Kontrolní otázka: Co se stane po prvním a po druhém kliknutí?
Pro řešení je nutno chápat, že přiřazení anonymní metody způsobí, že lokální proměnné jsou zachyceny a žijí tak dlouho jako anonymní metoda. Je to řešeno nějak přes interface a počítání referencí a to tak, že jsou lokální proměnné zkopírovány včetně parametrů a dál si žijí a anonymní metoda s nimi dál pracuje.
Tj. po kliknutí se vypíše
Button2Click IntProc:8
[OK]
Button2Click IntProc2:11
[OK]
Po dalším kliknutí se hodnoty změní na 15 a 18.
Nyní je patrné, že je pravda co jsem napsal a lokální proměnná i je někde uložena a anonymní metoda s ní může pracovat. Podle mne je toto zachování stavu pro pozdější použití důležitá a zajímavá vlastnost.
Minule jsem psal, že hodně síly chápu v synchronizacích např. GUI. Doporučoval jsem je používat pro synchronize, ale nějak jsem opomněl
TThread.Queue(AThreadProc: TThreadProcedure)
což je asynchronní synchronizace UI v hlavním vlákně.
Nehledě na jejich použití vzhledem k paralelnímu programování.