& # 8220; El lado izquierdo no se puede asignar a & # 8221; para las propiedades de tipo de registro en Delphi
Pregunta
Tengo curiosidad por saber por qué Delphi trata las propiedades de tipo de registro como de solo lectura:
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;
Si intento asignar un valor a cualquiera de los miembros de la propiedad Rec, obtendré que " El lado izquierdo no se puede asignar a " error:
procedure TForm1.DoSomething(ARec: TRec);
begin
Rec.A := ARec.A;
end;
mientras se hace lo mismo con el campo subyacente está permitido:
procedure TForm1.DoSomething(ARec: TRec);
begin
FRec.A := ARec.A;
end;
¿Hay alguna explicación para ese comportamiento?
Solución
Desde " Rec " es una propiedad, el compilador la trata de forma un poco diferente porque primero tiene que evaluar la " leer " de la propiedad decl. Considera esto, que es semánticamente equivalente a tu ejemplo:
...
property Rec: TRec read GetRec write FRec;
...
Si lo ves así, puedes ver que la primera referencia a " Rec " (antes del punto '.'), debe llamar a GetRec, que creará una copia local temporal de Rec. Estos temporales son por diseño " solo lectura. & Quot; Esto es lo que estás encontrando.
Otra cosa que puedes hacer aquí es dividir los campos individuales del registro como propiedades en la clase contenedora:
...
property RecField: Integer read FRec.A write FRec.A;
...
Esto le permitirá asignar directamente a través de la propiedad al campo de ese registro incrustado en la instancia de la clase.
Otros consejos
Sí, esto es un problema. Pero el problema se puede resolver utilizando las propiedades de registro:
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;
Esto compila y funciona sin problema.
Una solución que utilizo con frecuencia es declarar la propiedad como un puntero al 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;
Con esto, la asignación directa de Form1.Rec.A: = MyInteger
funcionará, pero también Form1.Rec: = MyRec
funcionará copiando todos los valores en < código> MyRec al campo FRec
como se esperaba.
El único inconveniente aquí es que cuando desea recuperar una copia del registro para trabajar, tendrá que hacer algo como MyRec: = Form1.Rec ^
El compilador le está impidiendo asignar a un temporal. Se permite el equivalente en C #, pero no tiene efecto; el valor de retorno de la propiedad Rec es una copia del campo subyacente, y la asignación al campo en la copia es nop.
Porque tiene funciones implícitas de captador y establecedor y no puede modificar el Resultado de una función ya que es un parámetro const.
(Nota: En caso de que transformes el registro en un objeto, el resultado sería un puntero, equivalente a un parámetro var).
Si desea permanecer con un Registro, debe usar una variable intermedia (o la variable Campo) o usar una declaración WITH.
Vea los diferentes comportamientos en el siguiente código con las funciones explícitas de getter y 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;
Esto se debe a que las propiedades se cumplen realmente como una función. Las propiedades solo devuelven o establecen un valor. No es una referencia o un puntero al registro
entonces:
Testing.TestRecord.I := 10; // error
es lo mismo que llamar a una función como esta:
Testing.getTestRecord().I := 10; //error (i think)
lo que puedes hacer es:
r := Testing.TestRecord; // read
r.I := 10;
Testing.TestRecord := r; //write
Es un poco desordenado pero inherente a este tipo de arquitectura.
Como han dicho otros, la propiedad de lectura devolverá una copia del registro, por lo que la asignación de campos no está actuando sobre la copia propiedad de TForm1.
Otra opción es 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 eliminará el puntero PRec para ti, así que cosas como estas seguirán funcionando:
Form1.Rec.A := 1234;
No es necesario escribir una parte de la propiedad, a menos que desee intercambiar el búfer PRec al que FRec apunta. Realmente no sugeriría hacer tal intercambio a través de una propiedad de todos modos.
El enfoque más simple es:
procedure TForm1.DoSomething(ARec: TRec);
begin
with Rec do
A := ARec.A;
end;