I'm curious to know why Delphi treats record type properties as read only:
TRec = record
A : integer;
B : string;
end;
TForm1 = class(TForm)
private
FRec : TRec;
public
procedure DoSomething(ARec: TRec);
property Rec : TRec read FRec write FRec;
end;
If I try to assign a value to any of the members of Rec property, I'll get "Left side cannot be assigned to" error:
procedure TForm1.DoSomething(ARec: TRec);
begin
Rec.A := ARec.A;
end;
while doing the same with the underlying field is allowed:
procedure TForm1.DoSomething(ARec: TRec);
begin
FRec.A := ARec.A;
end;
Is there any explanation for that behavior?
Since "Rec" is a property, the compiler treats it a little differently because it has to first evaluate the "read" of the property decl. Consider this, which is semantically equivalent to your example:
...
property Rec: TRec read GetRec write FRec;
...
If you look at it like this, you can see that the first reference to "Rec" (before the dot '.'), has to call GetRec, which will create a temporary local copy of Rec. These temporaries are by design "read-only." This is what you're running into.
Another thing you can do here is to break out the individual fields of the record as properties on the containing class:
...
property RecField: Integer read FRec.A write FRec.A;
...
This will allow you to directly assign through the property to the field of that embedded record in the class instance.
Yes this is a problem. But the problem can be solved using record properties:
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;
This compiles and workes without problem.
A solution I frequently use is to declare the property as a pointer to the record.
type
PRec = ^TRec;
TRec = record
A : integer;
B : string;
end;
TForm1 = class(TForm)
private
FRec : TRec;
function GetRec: PRec;
procedure SetRec(Value: PRec);
public
property Rec : PRec read GetRec write SetRec;
end;
implementation
function TForm1.GetRec: PRec;
begin
Result := @FRec;
end;
procedure TForm1.SetRec(Value: PRec);
begin
FRec := Value^;
end;
With this, directly assigning Form1.Rec.A := MyInteger
will work, but also Form1.Rec := MyRec
will work by copying all the values in MyRec
to the FRec
field as expected.
The only pitfall here is that when you wish to actually retrieve a copy of the record to work with, you will have to something like MyRec := Form1.Rec^
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