& # 8220; La parte sinistra non può essere assegnata a & # 8221; per le proprietà del tipo di record in Delphi

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

  •  05-07-2019
  •  | 
  •  

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?

È stato utile?

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;
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top