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?
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With