"Левая сторона не может быть назначена" для свойств типа записи в 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;
есть ли какое-либо объяснение этому поведению?
в отношении
8 ответов
поскольку " Rec "является свойством, компилятор обрабатывает его немного по-другому, потому что он должен сначала оценить" чтение " свойства decl. Рассмотрим это, что семантически эквивалентно вашему примеру:
...
property Rec: TRec read GetRec write FRec;
...
Если вы посмотрите на это так, вы увидите, что первая ссылка на "Rec" (перед точкой '.'), должен вызвать GetRec, который создаст временную локальную копию Rec. Эти временные файлы предназначены только для чтения.- Это то, чем ты занимаешься. в.
еще одна вещь, которую вы можете сделать здесь, - это разбить отдельные поля записи как свойства на содержащем классе:
...
property RecField: Integer read FRec.A write FRec.A;
...
Это позволит вам напрямую назначить через свойство поле этой встроенной записи в экземпляре класса.
Да это проблема. Но проблема может быть решена с помощью свойств записи:
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;
это компилируется и работает без проблем.
компилятор останавливает вас от назначения временному. Эквивалент в C# разрешен, но он не имеет никакого эффекта; возвращаемое значение свойства Rec является копией базового поля, а назначение полю в копии-nop.
решение, которое я часто использую, - объявить свойство как указатель на запись.
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^
потому что у вас есть неявные функции геттера и сеттера, и вы не можете изменить результат функции, поскольку это параметр const.
(Примечание: Если вы преобразуете запись в объект, результат будет фактически указателем, что эквивалентно параметру var).
Если вы хотите остаться с записью, вы должны использовать промежуточную переменную (или переменную поля) или использовать оператор WITH.
см. различные модели поведения в следующем код с явными функциями геттера и сеттера:
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;
это потому, что свойство фактически выполняется как функция. Свойства только возвращать значение. Это не ссылка или указатель на запись
так :
Testing.TestRecord.I := 10; // error
это то же самое, что и вызов такой функции:
Testing.getTestRecord().I := 10; //error (i think)
что вы можете сделать, это:
r := Testing.TestRecord; // read
r.I := 10;
Testing.TestRecord := r; //write
это немного грязно, но присуще этому типу архитектуры.
Как говорили другие-свойство 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. Я бы не советовал делать такое обмен через свойство в любом случае.
самый простой подход-это:
procedure TForm1.DoSomething(ARec: TRec);
begin
with Rec do
A := ARec.A;
end;