& # 8220; El lado izquierdo no se puede asignar a & # 8221; para las propiedades de tipo de registro en Delphi

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

  •  05-07-2019
  •  | 
  •  

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?

¿Fue útil?

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;
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top