& # 8220; Левая сторона не может быть назначена & # 8221; для свойств типа записи в Delphi

StackOverflow https://stackoverflow.com/questions/620506

  •  05-07-2019
  •  | 
  •  

Вопрос

Мне любопытно узнать, почему 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;
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top