2009-03-06 14 views
16

Tengo curiosidad por saber por qué Delphi trata propiedades de tipo de registro como de sólo lectura:"lado izquierdo no se puede asignar a" para las propiedades de tipo de registro en 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; 

Si trato de asignar un valor a cualquiera de los miembros de la propiedad Rec, voy a conseguir "lado izquierdo no se puede asignar a un" error:

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

mientras se hace lo mismo con el campo subyacente se permite:

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

¿Hay alguna explicación para ese comportamiento?

Saludos

Respuesta

27

Desde "Rec" es una propiedad, el compilador lo trata de manera diferente porque tiene que evaluar primero el "leer" de las decl propiedad. Considere esto, que es semánticamente equivalente a su ejemplo:

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

Si nos fijamos en que de esta manera, se puede ver que la primera referencia a "Rec" (antes del punto ''), tiene que llamar GetRec , que creará una copia local temporal de Rec. Estos temporales son, por diseño, "de solo lectura". Esto es con lo que te estás metiendo.

Otra cosa que puedes hacer aquí es romper los campos individuales del registro como propiedades en la clase que contiene:

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

Esto le permitirá asignar directamente a través de la propiedad en el campo de ese incrustado grabar en la instancia de la clase.

+1

1 Topamos con este 4 años después de su respuesta! –

4

Porque tiene funciones implícitas getter y setter y no puede modificar el resultado de una función ya que es un parámetro const.

(Nota: en caso de que se transforme el registro en un objeto, el resultado sería en realidad un puntero, por lo tanto, equivalente a un parámetro var).

Si desea permanecer con un registro, debe utilizar una variable intermedia (o la variable de campo) o utilizar una instrucción WITH.

ver las distintas conductas en el siguiente código con las funciones get y 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; 
19

Sí, esto es un problema. Pero el problema se puede resolver usando 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 
    { Private declarations } 
    FRec : TRec; 
public 
    { Public declarations } 
    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 problemas.

+3

+1 Tenga en cuenta que su solución no es mala, pero los usuarios deben recordar que si alguna vez cambian la propiedad a "propiedad Rec: TRec lee GetRec write FRec;", el truco de asignación fallará miserablemente (porque GetRec devolverá un * copie * ya que los registros son * tipos de valores *). –

+0

La propiedad Rec en TForm1 solo se puede leer si solo se requiere acceso de lectura/escritura a las propiedades del registro. La parte clave de esta solución son los métodos setter en las propiedades del registro. – Griffyn

8

El compilador le impide asignar a un temporal. El equivalente en C# está permitido, pero no tiene ningún 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 un nop.

2

Como otros han dicho, la propiedad de lectura devolverá una copia del registro, por lo que la asignación de campos no está actuando en la copia propiedad de TForm1.

Otra opción es algo así 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 la referencia al puntero PREC para usted, por lo que este tipo de cosas seguirán funcionando:

Form1.Rec.A := 1234; 

No hay necesidad de que una parte de escritura de la propiedad, a menos que desee intercambiar el búfer PRec que señala FRec. Realmente no sugeriría hacer tal intercambio a través de una propiedad de todos modos.

2

El enfoque más simple es:

procedure TForm1.DoSomething(ARec: TRec); 
begin 
    with Rec do 
    A := ARec.A; 
end; 
+0

Creo que tienes razón, no tiene sentido usar propiedades para registros, parece mucho trabajo ... solo tienes un procedimiento que hace algo para un registro: SetSomething (var ARec: TRec) – sergeantKK

3

Esto se debe a la propiedad son en realidad cumplió como una función. Las propiedades solo devuelven o establecen un valor. No es una referencia o un puntero al registro

manera:

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

es igual que llamar a una función como esta:

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

lo que puede hacer es:

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

Es un poco complicado pero inherente a este tipo de arquitectura.

7

Una solución que uso 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 Form1.Rec.A := MyInteger funcionará, sino también Form1.Rec := MyRec funcionará copiando todos los valores en MyRec al campo FRec como se esperaba.

El único escollo es que cuando se desea recuperar en realidad una copia del registro de trabajar, tendrá que algo así como MyRec := Form1.Rec^

+0

Excelente y profesional – Vassilis

Cuestiones relacionadas