“O lado esquerdo não pode ser atribuído a” para propriedades tipo de registro no Delphi
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?
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;