& # 8220; Левая сторона не может быть назначена & # 8221; для свойств типа записи в 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;
Есть ли объяснение этому поведению?
Решение
Поскольку " Rec " является свойством, компилятор обрабатывает его немного по-другому, потому что он должен сначала оценить «read» собственности Учтите это, что семантически эквивалентно вашему примеру:
...
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
FRec : TRec;
public
property Rec : TRec read FRec write FRec;
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
Rec.A := 21;
Rec.B := 'Hi';
end;
Это компилирует и работает без проблем.
Решением, которое я часто использую, является объявление свойства как указателя на запись.
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
, копируя все значения в < code> MyRec в поле FRec
, как и ожидалось.
Единственный подводный камень здесь заключается в том, что когда вы действительно хотите получить копию записи для работы, вам нужно что-то вроде MyRec: = Form1.Rec ^
Компилятор мешает вам назначить временный. Эквивалент в C # разрешен, но он не имеет никакого эффекта; возвращаемое значение свойства Rec - это копия базового поля, а присвоение полю в копии - нет.
Поскольку у вас есть неявные функции получения и установки, и вы не можете изменить результат функции, поскольку он является константным параметром. Р>
(Примечание. В случае, если вы преобразуете запись в объект, результатом будет фактически указатель, что эквивалентно параметру var).
Если вы хотите остаться с записью, вы должны использовать промежуточную переменную (или переменную Field) или оператор 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;