6

In Delphi, you can apparently assign an entire chain of method calls to a single variable:

program What;
{$APPTYPE CONSOLE}

type
  TProc = reference to procedure();

  TRecord = record
    procedure MethodOfRecord();
  end;

procedure TRecord.MethodOfRecord();
begin
  WriteLn('MethodOfRecord finished');
end;

function MakeRecord(): TRecord;
begin
  WriteLn('    MakeRecord finished');
end;

var
  proc: TProc;
begin
  proc := MakeRecord().MethodOfRecord;
  proc();
  proc();
  proc();
end.

In this code, I create an anonymous TRecord, assign its method TRecord.MethodOfRecord to a reference to procedure, and then call it 3 times.

Question: How many times will MakeRecord be called?

Answer: 3 times:

    MakeRecord finished
MethodOfRecord finished
    MakeRecord finished
MethodOfRecord finished
    MakeRecord finished
MethodOfRecord finished

Every time I call proc, it calls MakeRecord first. This seems weird. Why does this happen? I expected it to be called only once. Is it because it's anonymous? Of course if I give the TRecord a name, it gets called only once:

var
  proc: TProc;
  rec: TRecord;
begin
  rec := MakeRecord();
  proc := rec.MethodOfRecord;
  proc();
  proc();
  proc();
end.

this outputs:

    MakeRecord finished
MethodOfRecord finished
MethodOfRecord finished
MethodOfRecord finished

Can someone explain the logic behind this behavior? Is this documented somewhere?

This is Embarcadero® Delphi 10.3.

Update:

This works not only with reference to procedure(), but also with reference to any function that can take arguments and return values, for example with TProc = reference to function(s: string): string;.

program What;
{$APPTYPE CONSOLE}

uses System.SysUtils;

type
  TProc = reference to function(s: string): string;

  TRecord = record
    FirstHalf: string;
    function Combine(secondHalf: string): string;
  end;

function TRecord.Combine(secondHalf: string): string;
begin
  Result := Format('%s + %s', [FirstHalf, secondHalf]);
  WriteLn(Format('   Combine finished with secondHalf = %s', [secondHalf]));
end;

function MakeRecord(firstHalf: string): TRecord;
begin
  Result.FirstHalf := firstHalf;
  WriteLn(Format('MakeRecord finished with  firstHalf = %s', [firstHalf]));
end;

var
  proc: TProc;
  msg: string;
begin
  proc := MakeRecord(msg).Combine;
  msg := 'A';
  WriteLn(proc('a'));
  msg := 'B';
  WriteLn(proc('b'));
  msg := 'C';
  WriteLn(proc('c'));
end.

This outputs:

MakeRecord finished with  firstHalf = A
   Combine finished with secondHalf = a
A + a
MakeRecord finished with  firstHalf = B
   Combine finished with secondHalf = b
B + b
MakeRecord finished with  firstHalf = C
   Combine finished with secondHalf = c
C + c
spiderface
  • 1,025
  • 2
  • 11
  • 16
  • 1
    Strange. In XE2 this doesn't compile at all. Anyway, you probably want "MethodOfRecord" to be a class procedure, then you'd assign like "proc := TRecord.MethodOfRecord". – Sertac Akyuz May 24 '20 at 03:53

1 Answers1

9
proc := MakeRecord().MethodOfRecord;

Here proc is an anonymous method, but MethodOfRecord is not, it's a normal procedure. You might anticipate a compilation error, but the compiler does a little work in the background for you. It turns your code into this:

proc := 
  procedure 
  begin 
    MakeRecord().MethodOfRecord; 
  end;

Now the right hand side is an anonymous method, and you can see why your program behaves as it does.

If you don't want a new record to be created on each invocation, you'd need to declare a local variable for the record instance that you want to be used.

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • It's most likely what is happening, but is this documented behavior? Nothing [here](http://docwiki.embarcadero.com/RADStudio/Rio/en/Anonymous_Methods_in_Delphi) mentions such a construct. – Olivier May 24 '20 at 07:58
  • @Olivier This is what is happening. I don't know about the documentation, whether anything official exists. – David Heffernan May 24 '20 at 09:03
  • @DavidHeffernan I suppose this "wrapping" works not only with `reference to procedure()`, but also with reference to any argumentless function? – spiderface May 24 '20 at 15:57
  • Doesn't even need to be parameterless – David Heffernan May 24 '20 at 16:02