Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Delphi, record type property, record field assignment: Assignment to local copy of record expected

Over at the question “Left side cannot be assigned to” for record type properties in Delphi, there is an answer from Toon Krijthe demonstrating how assignments to fields of a record property can be done by using properties in the declaration of the record. For easier reference, here is the code snippet published by Toon Krijthe.

type
  TRec = record
  private
    FA : integer;
    FB : string;
    procedure SetA(const Value: Integer);
    procedure SetB(const Value: string);
  public
    property A: Integer read FA write SetA;
    property B: string read FB write SetB;
  end;

procedure TRec.SetA(const Value: Integer);
begin
  FA := Value;
end;

procedure TRec.SetB(const Value: string);
begin
  FB := Value;
end;

TForm1 = class(TForm)
  Button1: TButton;
  procedure Button1Click(Sender: TObject);
private
  FRec : TRec;
public
  property Rec : TRec read FRec write FRec;
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  Rec.A := 21;
  Rec.B := 'Hi';
end;

It is clear to me why the "Left side cannot be assigned to" error is raised in the original code of vcldeveloper without the setter in the record. It is also clear to me why no error is raised for the assignment Rec.A := 21; if a setter is defined for the property TRec.A like in the case of the code above.

What I do not understand is why the assignment Rec.A := 21; assigns the value 21 to the field FRec.FA of TForm1. I would have expected that the value is assigned to the field FA of a local temporary copy of FRec but not FRec.FA itself. Could anyone please shed some light on what is happening here?

like image 366
J. Hauser Avatar asked Mar 05 '19 09:03

J. Hauser


1 Answers

This is a great question!

The behaviour you see is a consequence of the implementation details for properties. The way the compiler implements properties differs for direct field property getters and for function property getters.

When you write

Rec.A := 21;

The compiler sees Rec and knows that it is a property. Since the getter is a direct field getter, the compiler simply replaces Rec with FRec and compiles the code exactly as if you had written

FRec.A := 21;

The compiler then encounters the A property and uses the setter method, and so your assignment becomes

FRec.SetA(21);

Hence the behaviour that you observed.

Suppose that instead of a direct field getter you had a function getter

property Rec: TRec read GetRec;
....
function TForm1.GetRec: TRec;
begin
  Result := FRec;
end;

In that scenario the handling of

Rec.A := 21;

changes. The compiler instead declares an implicit local variable and the code is compiled like this:

var
  __local_rec: TRec;
....
__local_rec := GetRec;
__local_rec.A := 21;

It seems obvious to me that the behaviour of such a program should not depend on whether the property getter is a direct field getter or a function getter. This seems like a design flaw in the interaction between the property feature and the enhanced records feature.


Here is a complete program that demonstrates the issue very succinctly:

{$APPTYPE CONSOLE}

type
  TRec = record
  private
    FA: Integer;
    procedure SetA(const Value: integer);
  public
    property A: integer read FA write SetA;
  end;

procedure TRec.SetA(const Value: integer);
begin
  FA := Value;
end;

type
  TMyClass = class
  private
    FRec: TRec;
    function GetRec: TRec;
  public
    property RecDirect: TRec read FRec;
    property RecFunction: TRec read GetRec;
  end;

var
  Obj: TMyClass;

function TMyClass.GetRec: TRec;
begin
  Result := FRec;
end;

begin
  Obj := TMyClass.Create;
  Obj.RecDirect.A := 21;
  Writeln(Obj.FRec.FA);

  Obj := TMyClass.Create;
  Obj.RecFunction.A := 21;
  Writeln(Obj.FRec.FA);
end.

Output

21
0
like image 77
David Heffernan Avatar answered Oct 25 '22 22:10

David Heffernan