"Левая сторона не может быть назначена" для свойств типа записи в Delphi
Мне любопытно узнать, почему Delphi рассматривает свойства типа записи как прочитанные только:
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;
Если я попытаюсь присвоить значение любому из членов свойства Rec, я получу "Левая сторона не может быть назначена":
procedure TForm1.DoSomething(ARec: TRec);
begin
Rec.A := ARec.A;
end;
делая то же самое с базовым полем:
procedure TForm1.DoSomething(ARec: TRec);
begin
FRec.A := ARec.A;
end;
Есть ли объяснения этого поведения?
Привет
Ответы
Ответ 1
Так как "Rec" является свойством, компилятор рассматривает его несколько иначе, потому что он должен сначала оценить "чтение" свойства decl. Рассмотрим это, что семантически эквивалентно вашему примеру:
...
property Rec: TRec read GetRec write FRec;
...
Если вы посмотрите на это так, вы увидите, что первая ссылка на "Rec" (перед точкой "." ) должна вызвать GetRec, которая создаст временную локальную копию Rec. Эти временные рамки по дизайну "только для чтения". Это то, над чем вы работаете.
Еще одна вещь, которую вы можете сделать здесь, состоит в том, чтобы разбить отдельные поля записи как свойства в содержащем классе:
...
property RecField: Integer read FRec.A write FRec.A;
...
Это позволит вам напрямую назначить через свойство поле этой встроенной записи в экземпляре класса.
Ответ 2
Да, это проблема. Но проблема может быть решена с использованием свойств записи:
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
{ Private declarations }
FRec : TRec;
public
{ Public declarations }
property Rec : TRec read FRec write FRec;
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
Rec.A := 21;
Rec.B := 'Hi';
end;
Это компилируется и работает без проблем.
Ответ 3
Компилятор останавливает вас от назначения временному. Эквивалент в С# разрешен, но он не действует; возвращаемое значение свойства Rec является копией базового поля, а присвоение поля на копии является nop.
Ответ 4
Часто используемым решением является объявление свойства как указателя на запись.
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;
При этом будет выполняться прямое назначение Form1.Rec.A := MyInteger
, но также Form1.Rec := MyRec
будет работать, скопировав все значения в MyRec
в поле FRec
, как ожидалось.
Единственная ошибка здесь заключается в том, что, когда вы хотите фактически получить копию записи для работы, вам придется что-то вроде MyRec := Form1.Rec^
Ответ 5
Поскольку у вас есть неявные функции getter и setter, и вы не можете изменить результат функции, поскольку это параметр const.
(Примечание. Если вы преобразуете запись в объект, результатом будет фактически указатель, что эквивалентно параметру var).
Если вы хотите остаться с записью, вам нужно использовать промежуточную переменную (или переменную поля) или использовать инструкцию WITH.
См. различные типы поведения в следующем коде с явными функциями getter и setter:
type
TRec = record
A: Integer;
B: string;
end;
TForm2 = class(TForm)
private
FRec : TRec;
FRec2: TRec;
procedure SetRec2(const Value: TRec);
function GetRec2: TRec;
public
procedure DoSomething(ARec: TRec);
property Rec: TRec read FRec write FRec;
property Rec2: TRec read GetRec2 write SetRec2;
end;
var
Form2: TForm2;
implementation
{$R *.dfm}
{ TForm2 }
procedure TForm2.DoSomething(ARec: TRec);
var
LocalRec: TRec;
begin
// copy in a local variable
LocalRec := Rec2;
LocalRec.A := Arec.A; // works
// try to modify the Result of a function (a const) => NOT ALLOWED
Rec2.A := Arec.A; // compiler refused!
with Rec do
A := ARec.A; // works with original property and with!
end;
function TForm2.GetRec2: TRec;
begin
Result:=FRec2;
end;
procedure TForm2.SetRec2(const Value: TRec);
begin
FRec2 := Value;
end;
Ответ 6
Это потому, что свойство фактически выполняется как функция. Свойства возвращают или устанавливают значение. Это не ссылка или указатель на запись
так:
Testing.TestRecord.I := 10; // error
аналогичен вызову функции, подобной этой:
Testing.getTestRecord().I := 10; //error (i think)
что вы можете сделать:
r := Testing.TestRecord; // read
r.I := 10;
Testing.TestRecord := r; //write
Это немного грязный, но присущий этому типу архитектуры.
Ответ 7
Как и другие, - свойство read вернет копию записи, поэтому присвоение полей не действует на копию, принадлежащую TForm1.
Другой вариант:
TRec = record
A : integer;
B : string;
end;
PRec = ^TRec;
TForm1 = class(TForm)
private
FRec : PRec;
public
constructor Create;
destructor Destroy; override;
procedure DoSomething(ARec: TRec);
property Rec : PRec read FRec;
end;
constructor TForm1.Create;
begin
inherited;
FRec := AllocMem(sizeof(TRec));
end;
destructor TForm1.Destroy;
begin
FreeMem(FRec);
inherited;
end;
Delphi будет разыменовывать указатель PRec для вас, поэтому такие вещи будут работать:
Form1.Rec.A := 1234;
Нет необходимости записывать часть свойства, если вы не захотите поменять буфер PRec, на который указывает FRec. Я действительно не предлагал бы делать такую замену через свойство в любом случае.
Ответ 8
Самый простой способ:
procedure TForm1.DoSomething(ARec: TRec);
begin
with Rec do
A := ARec.A;
end;