& # 8220; La parte sinistra non può essere assegnata a & # 8221; per le proprietà del tipo di record in Delphi
Domanda
Sono curioso di sapere perché Delphi tratta le proprietà del tipo di record in sola lettura:
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 provo ad assegnare un valore a uno qualsiasi dei membri della proprietà Rec, visualizzerò " La parte sinistra non può essere assegnata a " Errore:
procedure TForm1.DoSomething(ARec: TRec);
begin
Rec.A := ARec.A;
end;
mentre si fa lo stesso con il campo sottostante è consentito:
procedure TForm1.DoSomething(ARec: TRec);
begin
FRec.A := ARec.A;
end;
C'è qualche spiegazione per quel comportamento?
Soluzione
Poiché " Rec " è una proprietà, il compilatore la tratta in modo leggermente diverso perché deve prima valutare la "lettura" della proprietà decl. Considera questo, che è semanticamente equivalente al tuo esempio:
...
property Rec: TRec read GetRec write FRec;
...
Se lo guardi in questo modo, puoi vedere che il primo riferimento a & Rec; Rec " (prima del punto '.'), deve chiamare GetRec, che creerà una copia locale temporanea di Rec. Questi provvisori sono di progettazione "sola lettura". Questo è quello che stai incontrando.
Un'altra cosa che puoi fare qui è dividere i singoli campi del record come proprietà della classe contenitore:
...
property RecField: Integer read FRec.A write FRec.A;
...
Ciò ti consentirà di assegnare direttamente tramite la proprietà il campo di quel record incorporato nell'istanza della classe.
Altri suggerimenti
Sì, questo è un problema. Ma il problema può essere risolto utilizzando le proprietà dei record:
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;
Questo compila e funziona senza problemi.
Una soluzione che utilizzo frequentemente è dichiarare la proprietà come puntatore al record.
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 questo, l'assegnazione diretta di Form1.Rec.A: = MyInteger
funzionerà, ma anche Form1.Rec: = MyRec
funzionerà copiando tutti i valori in < code> MyRec nel campo FRec
come previsto.
L'unica trappola qui è che quando si desidera recuperare effettivamente una copia del record con cui lavorare, sarà necessario qualcosa come MyRec: = Form1.Rec ^
Il compilatore ti impedisce di assegnare a un temporaneo. L'equivalente in C # è consentito, ma non ha alcun effetto; il valore restituito dalla proprietà Rec è una copia del campo sottostante e l'assegnazione al campo nella copia è nop.
Perché hai implicite funzioni getter e setter e non puoi modificare il risultato di una funzione in quanto è un parametro const.
(Nota: nel caso in cui trasformi il record in un oggetto, il risultato sarebbe effettivamente un puntatore, quindi equivalente a un parametro var).
Se vuoi rimanere con un Record, devi usare una variabile intermedia (o la variabile Field) o usare un'istruzione WITH.
Vedi i diversi comportamenti nel codice seguente con le funzioni esplicite getter e 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;
Questo perché le proprietà sono effettivamente rispettate come funzione. Le proprietà restituiscono o impostano solo un valore. Non è un riferimento o un puntatore al record
quindi:
Testing.TestRecord.I := 10; // error
equivale a chiamare una funzione come questa:
Testing.getTestRecord().I := 10; //error (i think)
quello che puoi fare è:
r := Testing.TestRecord; // read
r.I := 10;
Testing.TestRecord := r; //write
È un po 'disordinato ma inerente a questo tipo di architettura.
Come altri hanno già detto, la proprietà read restituirà una copia del record, quindi l'assegnazione dei campi non agisce sulla copia di proprietà di TForm1.
Un'altra opzione è qualcosa del tipo:
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 dereferenzerà il puntatore PRec per te, quindi cose come queste continueranno a funzionare:
Form1.Rec.A := 1234;
Non è necessaria una parte di scrittura della proprietà, a meno che non si desideri scambiare il buffer PRec a cui fa riferimento FRec. Non suggerirei davvero di fare un tale scambio tramite una proprietà comunque.
L'approccio più semplice è:
procedure TForm1.DoSomething(ARec: TRec);
begin
with Rec do
A := ARec.A;
end;