Подтвердить что ты не робот

"Левая сторона не может быть назначена" для свойств типа записи в 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;

Есть ли объяснения этого поведения?

Привет

4b9b3361

Ответ 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;