“O lado esquerdo não pode ser atribuído a” para propriedades tipo de registro no Delphi

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

  •  05-07-2019
  •  | 
  •  

Pergunta

Estou curioso para saber por que Delphi trata propriedades tipo de registro como somente leitura:

  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;

Se eu tentar atribuir um valor a qualquer um dos membros do Rec propriedade, eu vou pegar "Lado esquerdo não pode ser atribuído a" erro:

procedure TForm1.DoSomething(ARec: TRec);
begin
  Rec.A := ARec.A;
end;

ao fazer o mesmo com o campo subjacente é permitido:

procedure TForm1.DoSomething(ARec: TRec);
begin
  FRec.A := ARec.A;
end;

Existe alguma explicação para esse comportamento?

Foi útil?

Solução

Uma vez que "Rec" é uma propriedade, os compilador trata um pouco diferente porque tem que primeiro avaliar a "ler" do decl propriedade. Considere isso, que é semanticamente equivalente a seu exemplo:

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

Se você olhar para ele como este, você pode ver que a primeira referência a "Rec" (antes do ponto ''), tem que chamar GetRec, que criará uma cópia local temporária de Rec. Estes temporários são por design "read-only". Isto é o que você está executando em.

Outra coisa que você pode fazer aqui é para quebrar os campos individuais do registro como propriedades na classe que contém:

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

Isso permitirá que você atribua diretamente através da propriedade para o campo desse registro incorporado na instância da classe.

Outras dicas

Sim, este é um problema. Mas o problema pode ser resolvido usando as propriedades de gravação:

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;

Isso compila e workes sem problema.

A solução que eu freqüentemente uso é para declarar a propriedade como um ponteiro para o registro.

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;

Com isso, diretamente atribuindo Form1.Rec.A := MyInteger vai funcionar, mas também Form1.Rec := MyRec vai trabalhar copiando todos os valores em MyRec ao campo FRec como esperado.

A única dificuldade aqui é que quando você quiser realmente recuperar uma cópia do registro de trabalhar, você terá que algo como MyRec := Form1.Rec^

O compilador está parando de atribuir a um temporário. O equivalente em C # é permitido, mas não tem nenhum efeito; o valor de retorno da propriedade Rec é uma cópia do campo subjacente, e atribuindo ao campo na cópia é um nop.

Porque você tem funções implícitas get e set e você não pode modificar o resultado de uma função, pois é um parâmetro const.

. (Nota: No caso de você transformar o registro em um objeto, o resultado seria realmente um ponteiro, portanto, equivalente a um parâmetro var)

Se você quiser ficar com um registro, você tem que usar uma variável intermediária (ou a variável de campo) ou usar uma instrução WITH.

Veja os diferentes comportamentos no código a seguir com as funções get e set explícitas:

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;

Este é porque a propriedade são realmente cumpridas como uma função. Propriedades só retornar ou definir um valor. Não é uma referência ou um ponteiro para o registro

forma:

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

é o mesmo que chamar uma função como esta:

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

O que você pode fazer é:

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

É uma bagunça pouco, mas inerente a este tipo de arquitetura.

Como outros -. A propriedade de leitura irá retornar uma cópia do registro, de modo a atribuição de campos não está agindo na cópia de propriedade de TForm1

Outra opção é algo como:

  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 irá cancelar o ponteiro PRec para você, então coisas como esta continuará a funcionar:

Form1.Rec.A := 1234; 

Não há nenhuma necessidade para uma parte escrita do imóvel, a menos que você quiser trocar tamponar o PRec que os pontos de frec no. Eu realmente não iria sugerir a fazer tal troca através de uma propriedade de qualquer maneira.

A abordagem mais simples é:

procedure TForm1.DoSomething(ARec: TRec);
begin
  with Rec do
    A := ARec.A;
end;
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top