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