«Le côté gauche ne peut pas être affecté» pour les propriétés de type d'enregistrement dans Delphi

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

  •  05-07-2019
  •  | 
  •  

Question

Je suis curieux de savoir pourquoi Delphi traite les propriétés de type d'enregistrement en lecture seule:

  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 j'essaie d'attribuer une valeur à l'un des membres de la propriété Rec, j'obtiendrai "Le côté gauche ne peut pas être attribué à" erreur:

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

tout en faisant la même chose avec le champ sous-jacent est autorisé:

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

Existe-t-il une explication à ce comportement?

Était-ce utile?

La solution

Depuis " Rec " est une propriété, le compilateur la traite un peu différemment car il doit d’abord évaluer le paramètre "read" de la propriété décl. Considérez ceci, qui est sémantiquement équivalent à votre exemple:

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

Si vous le regardez comme ça, vous pouvez voir que la première référence à " Rec " (avant le point '.'), doit appeler GetRec, ce qui créera une copie locale temporaire de Rec. Ces temporaires sont conçus "en lecture seule". C'est ce que vous rencontrez.

Une autre chose que vous pouvez faire ici est de découper les champs individuels de l'enregistrement en tant que propriétés de la classe contenante:

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

Cela vous permettra d’affecter directement, par l’intermédiaire de la propriété, le champ de cet enregistrement incorporé à l’instance de la classe.

Autres conseils

Oui, c'est un problème. Mais le problème peut être résolu en utilisant les propriétés d’enregistrement:

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;

Ceci compile et fonctionne sans problème.

Une solution que j'utilise fréquemment consiste à déclarer la propriété en tant que pointeur sur l'enregistrement.

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;

Avec cela, assigner directement Form1.Rec.A: = MyInteger fonctionnera, mais aussi Form1.Rec: = MyRec fonctionnera en copiant toutes les valeurs de < code> MyRec dans le champ FRec comme prévu.

Le seul inconvénient ici est que lorsque vous souhaitez récupérer une copie de l'enregistrement avec lequel travailler, vous devrez utiliser quelque chose comme MyRec: = Form1.Rec ^

Le compilateur vous empêche d'attribuer à un temporaire. L'équivalent en C # est autorisé, mais il n'a pas d'effet; la valeur de retour de la propriété Rec est une copie du champ sous-jacent et l’affectation au champ de la copie est un nop.

Parce que vous avez des fonctions getter et setter implicites et que vous ne pouvez pas modifier le résultat d’une fonction car c’est un paramètre const.

(Remarque: si vous transformez l'enregistrement en objet, le résultat serait en fait un pointeur, équivalent à un paramètre var).

Si vous souhaitez conserver un enregistrement, vous devez utiliser une variable intermédiaire (ou la variable Field) ou une instruction WITH.

Voir les différents comportements dans le code suivant avec les fonctions explicites de lecture et de définition:

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;

Cela est dû au fait que les propriétés sont réellement respectées en tant que fonction. Les propriétés renvoient ou définissent uniquement une valeur. Ce n'est pas une référence ou un pointeur sur l'enregistrement

alors:

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

revient à appeler une fonction comme celle-ci:

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

Ce que vous pouvez faire est:

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

C’est un peu brouillon mais inhérent à ce type d’architecture.

Comme d'autres l'ont déjà dit, la propriété read retournera une copie de l'enregistrement. Par conséquent, l'attribution des champs n'agit pas sur la copie appartenant à TForm1.

Une autre option est quelque chose comme:

  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 va déréférencer le pointeur PRec pour vous, donc ce genre de chose fonctionnera toujours:

Form1.Rec.A := 1234; 

Il n’est pas nécessaire d’écrire la propriété, sauf si vous souhaitez échanger le tampon PRec sur lequel pointe FRec. De toute façon, je ne recommanderais pas de faire un tel échange via une propriété.

L’approche la plus simple est:

procedure TForm1.DoSomething(ARec: TRec);
begin
  with Rec do
    A := ARec.A;
end;
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top