2009-03-06 13 views
16

Sono curioso di sapere perché Delphi tratta di record proprietà del tipo di sola lettura:"lato sinistro non può essere assegnato a" per le proprietà di tipo record 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; 

Se provo ad assegnare un valore a una delle i membri di proprietà Rec, vado a prendere "lato sinistro non può essere assegnato a" errore:

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

mentre si fa la stessa cosa con il campo sottostante è consentito:

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

C'è qualche spiegazione per questo comportamento?

saluti

risposta

27

Da "Rec" è una struttura, il compilatore considera un po 'diverso perché deve valutare prima la "lettura" dei decl di proprietà. Considerate questo, che è semanticamente equivalente a tuo esempio:

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

Se si guarda in questo modo, si può vedere che il primo riferimento a "Rec" (prima che il punto ''), deve chiamare GetRec , che creerà una copia locale temporanea di Rec. Questi temporari sono di progettazione "di sola lettura". Questo è ciò che stai incontrando.

Un'altra cosa che puoi fare qui è quello di rompere i singoli campi del record come proprietà della classe che contiene:

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

Questo vi permetterà di assegnare direttamente tramite la proprietà al campo di quel incorporato registra nell'istanza della classe.

+1

+1 finito qui 4 anni dopo la tua risposta! –

4

Poiché si dispone di funzioni getter e setter implicite e non è possibile modificare il risultato di una funzione poiché è un parametro const.

(Nota: nel caso in cui si trasformi il record in un oggetto, il risultato sarebbe in realtà un puntatore, quindi equivalente a un parametro var).

Se si desidera mantenere un record, è necessario utilizzare una variabile intermedia (o la variabile Field) o utilizzare un'istruzione WITH.

vedere 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; 
19

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 
    { 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; 

Questo compila e funziona senza problemi.

+3

+1 Nota che la tua soluzione non è male, ma gli utenti devono ricordare che se cambiano la proprietà in "proprietà Rec: TRec leggi GetRec scrivi FRec;", il trucco dell'assegnazione fallirà miseramente (perché GetRec restituirà un * copia * come record sono * tipi di valore *). –

+0

La proprietà Rec in TForm1 può essere letta solo se è richiesto solo l'accesso in lettura/scrittura alle proprietà del record. La parte fondamentale di questa soluzione sono i metodi setter nelle proprietà del record. – Griffyn

8

Il compilatore impedisce all'utente di assegnare a un temporaneo. L'equivalente in C# è permesso, ma non ha alcun effetto; il valore di ritorno della proprietà Rec è una copia del campo sottostante e l'assegnazione al campo sulla copia è un nop.

2

Come altri hanno già detto, la proprietà di lettura restituirà una copia del record, quindi l'assegnazione dei campi non agisce sulla copia di proprietà di TForm1.

Un'altra opzione è qualcosa di simile:

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 dereference il puntatore prec per voi, quindi le cose come questo continueranno a funzionare:

Form1.Rec.A := 1234; 

Non c'è alcun bisogno di una parte di scrittura del proprietà, a meno che non si desideri scambiare il buffer PRec a cui punta FRec. Non vorrei assolutamente suggerire di fare un tale scambio tramite una proprietà.

2

L'approccio più semplice è:

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

Penso che tu abbia ragione - non ha senso usare le proprietà per i record, sembra un sacco di lavoro ... basta avere una procedura che faccia qualcosa su un record: SetSomething (var ARec: TRec) – sergeantKK

3

Questo è perché la proprietà sono in realtà rispettato come una funzione. Le proprietà restituiscono o impostano solo un valore. Non è un riferimento o un puntatore al record

così:

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

è stessa chiamare una funzione come questa:

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

cosa si può fare è:

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

È un po 'complicato ma inerente a questo tipo di architettura.

7

Una soluzione che uso 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 questa, direttamente assegnando Form1.Rec.A := MyInteger funzionerà, ma anche Form1.Rec := MyRec funzionerà copiando tutti i valori in MyRec al campo FRec come previsto.

L'unica insidia qui è che quando si desidera recuperare in realtà una copia del verbale con cui lavorare, si avrà a qualcosa di simile MyRec := Form1.Rec^

+0

Superbo e professionale – Vassilis

Problemi correlati