«Левая сторона не может быть назначена» для свойств типа записи в Delphi - PullRequest
19 голосов
/ 07 марта 2009

Мне любопытно узнать, почему 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 ]

34 голосов
/ 07 марта 2009

Поскольку «Rec» является свойством, компилятор обрабатывает его немного по-другому, потому что он должен сначала оценить «чтение» свойства decl. Учтите это, что семантически эквивалентно вашему примеру:

...
property Rec: TRec read GetRec write FRec;
...

Если вы посмотрите на это так, вы увидите, что первая ссылка на «Rec» (перед точкой «.») Должна вызвать GetRec, что создаст временную локальную копию Rec. Эти временные файлы предназначены только для чтения. Это то, с чем вы сталкиваетесь.

Еще одна вещь, которую вы можете сделать здесь, это выделить отдельные поля записи в качестве свойств содержащего класса:

...
property RecField: Integer read FRec.A write FRec.A;
...

Это позволит вам напрямую назначить через свойство этому полю встроенную запись в экземпляре класса.

19 голосов
/ 07 марта 2009

Да, это проблема. Но проблему можно решить, используя свойства записи:

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;

Это компилирует и работает без проблем.

10 голосов
/ 05 мая 2014

Решение, которое я часто использую, заключается в объявлении свойства как указателя на запись.

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^

7 голосов
/ 07 марта 2009

Компилятор мешает вам назначить временный. Эквивалент в C # разрешен, но он не имеет никакого эффекта; возвращаемое значение свойства Rec является копией нижележащего поля, а присвоение полю в копии - нет.

4 голосов
/ 07 марта 2009

Поскольку у вас есть неявные функции получения и установки, и вы не можете изменить результат функции, поскольку он является константным параметром.

(Примечание. В случае, если вы преобразуете запись в объект, результатом будет фактически указатель, что эквивалентно параметру 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;
3 голосов
/ 18 сентября 2012

Это потому, что свойство фактически выполняется как функция. Свойства только возвращают или устанавливают значение. Это не ссылка или указатель на запись

так:

Testing.TestRecord.I := 10;  // error

аналогично вызову такой функции:

Testing.getTestRecord().I := 10;   //error (i think)

что вы можете сделать:

r := Testing.TestRecord;    // read
r.I := 10;
Testing.TestRecord := r;    //write

Это немного грязно, но присуще архитектуре этого типа.

2 голосов
/ 10 ноября 2010

Самый простой подход:

procedure TForm1.DoSomething(ARec: TRec);
begin
  with Rec do
    A := ARec.A;
end;
2 голосов
/ 08 марта 2009

Как уже говорили другие, свойство 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. В любом случае, я бы не советовал делать такой обмен через свойство.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...