2009-03-06 3 views
16

Je suis curieux de savoir pourquoi Delphi traite les propriétés du type d'enregistrement en lecture seule:« côté gauche ne peut pas être attribué à » propriétés de type d'enregistrement dans 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 je tente d'attribuer une valeur à l'un des les membres de la propriété Rec, je vais obtenir « 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 permis:

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

Y a-t-il une explication à ce comportement?

Cordialement

Répondre

27

Depuis « Rec » est une propriété, le compilateur traite un peu différemment parce qu'il doit d'abord évaluer la « lecture » des décl de propriété. Considérez ceci, qui est sémantiquement équivalent à votre exemple:

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

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

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

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

Cela vous permettra d'attribuer directement à la propriété sur le terrain de cette embarqué enregistrer dans l'instance de classe.

+1

+1 dans ce 4 Bumped ans après votre réponse! –

4

Parce que vous avez des fonctions getter et setter implicites et que vous ne pouvez pas modifier le résultat d'une fonction car il s'agit d'un paramètre const. (Note: Si vous transformez l'enregistrement dans un objet, le résultat sera 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 utiliser une instruction WITH.

Voir les différents comportements dans le code suivant avec les getter explicites et fonctions 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

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

Cela compile et fonctionne sans problème.

+3

+1 Notez que votre solution n'est pas mauvaise, mais les utilisateurs doivent se rappeler que si jamais ils changent la propriété en "propriété Rec: TRec read GetRec write FRec;", la tâche d'affectation échouera lamentablement (parce que GetRec retournera * copier * comme les enregistrements sont * types de valeur *). –

+0

La propriété Rec dans TForm1 peut être lue uniquement si un accès en lecture/écriture aux propriétés de l'enregistrement est requis. La partie clé de cette solution sont les méthodes setter dans les propriétés de l'enregistrement. – Griffyn

8

Le compilateur vous empêche d'affecter à un temporaire. L'équivalent en C# est autorisé, mais il n'a aucun 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.

2

Comme d'autres l'ont dit - la propriété read renvoie une copie de l'enregistrement, de sorte que l'affectation des champs n'agit pas sur la copie détenue par 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 déréférence le pointeur PREC pour vous, des choses comme cela va encore travailler:

Form1.Rec.A := 1234; 

Il n'y a pas besoin d'une partie d'écriture de la propriété, à moins que vous ne souhaitiez échanger le tampon PRec pointé par FRec. Je ne suggère vraiment pas de faire un tel échange via une propriété de toute façon.

2

L'approche la plus simple est:

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

Je pense que vous avez raison - il ne sert à rien d'utiliser les propriétés pour les enregistrements, il semble que beaucoup de travail ... juste une procédure qui fait quelque chose à un enregistrement: SetSomething (var ARec: TRec) – sergeantKK

3

car les biens sont effectivement respectées en fonction. Les propriétés renvoient ou définissent une valeur uniquement. Il n'est pas une référence ou un pointeur à l'enregistrement

si:

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

est la même que l'appel d'une fonction comme ceci:

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.

7

Une solution que j'utilise fréquemment est de déclarer la propriété comme un pointeur vers 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, assignant directement Form1.Rec.A := MyInteger fonctionnera, mais aussi Form1.Rec := MyRec fonctionnera en copiant toutes les valeurs MyRec au champ FRec comme prévu.

Le seul écueil est que lorsque vous souhaitez récupérer en fait une copie du dossier de travailler avec, vous aurez à quelque chose comme MyRec := Form1.Rec^

+0

Superbe et professionnel – Vassilis

Questions connexes