Anonymous Methods & Closures

An anonymous method is a routine without a name, written inline, that can capture variables from its surrounding scope (a closure).

30.1 Method references

An anonymous method is a routine without a name, written inline, that can capture variables from its surrounding scope (a closure). Supported by Delphi 2009+; FPC supports them in the development version (3.3.1/trunk) with {$modeswitch functionreferences} and {$modeswitch anonymousfunctions} — not in FPC 3.2.x.

program Closures;
{$IFDEF FPC}
  {$mode delphi}
  {$modeswitch functionreferences}
  {$modeswitch anonymousfunctions}
{$ENDIF}
{$APPTYPE CONSOLE}
uses SysUtils;

type
  TIntFunc = reference to function(X: Integer): Integer;
  TCounterFunc = reference to function: Integer;

function MakeMultiplier(Factor: Integer): TIntFunc;
begin
  Result := function(X: Integer): Integer
            begin
              Result := X * Factor;     // Factor is captured
            end;
end;

function MakeCounter: TCounterFunc;
var
  Count: Integer;
begin
  Count := 0;
  Result := function: Integer
            begin
              Inc(Count);                // captured variable lives on after MakeCounter returns
              Result := Count;
            end;
end;

procedure ForEach(const A: array of Integer; F: TIntFunc);
var
  X: Integer;
begin
  for X in A do
    Write(F(X), ' ');
  Writeln;
end;

var
  Triple: TIntFunc;
  Next: TCounterFunc;
begin
  Triple := MakeMultiplier(3);
  ForEach([1, 2, 3], Triple);                 // 3 6 9
  ForEach([1, 2, 3], function(X: Integer): Integer
                     begin
                       Result := X * X;
                     end);                    // 1 4
…

30.2 Predefined reference types

Delphi System.SysUtils declares generic method references:

reference to procedure TProc<T>... TProc<T1..T4> procedure with 1–4 parameters

TFunc<TResult>... TFunc<T1..T4, TResult> function with 0–4 parameters

reference to function(Arg1: T): Boolean

30.3 How capture works

Captured variables are moved into a hidden, reference-counted heap object shared by the enclosing routine and all closures that capture them — capture is by reference, not by value.

Consequence: closures created in a loop all see the final value of the loop variable. Create each closure in a separate function to capture a copy:

A reference to variable can also hold ordinary routines and methods.

Anonymous methods are interfaces under the hood: they are reference-counted and freed automatically.

function MakePrinter(N: Integer): TProc;
begin
  Result := procedure begin Writeln(N); end;   // N is this call's own copy
end;

30.4 Typical uses

Callbacks and event handlers created at run time.

Sorting comparers (TComparer<T>.Construct).

Background work: TThread.CreateAnonymousThread(procedure begin ... end).Start;, TThread.Queue(nil, procedure begin ... end);, TTask.Run(...).

Deferred execution, retry helpers, and simple dependency injection.

30.5 Alternatives in FPC 3.2

Method pointers (procedure of object) with a small helper class holding the state.

Nested procedural variables ({$modeswitch nestedprocvars}, type procedure(X: Integer) is nested) — can access the enclosing routine's locals while it is still running (no capture beyond its lifetime).