Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Delphi reference to procedure weirdness

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
like image 518
spiderface Avatar asked May 24 '20 01:05

spiderface


Video Answer


1 Answers

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.

like image 142
David Heffernan Avatar answered Nov 13 '22 05:11

David Heffernan