2009-03-06 4 views
16

Ich bin neugierig zu wissen, warum Delphi Satzart Eigenschaften behandelt, als nur zu lesen:für Satzart Eigenschaften in Delphi „Linke Seite kann nicht zugewiesen werden“

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; 

Wenn ich versuche, einen Wert zu einem zuweisen die Mitglieder der Rec Eigenschaft, werde ich „Linke Seite kann nicht zugewiesen werden, um“ Fehler:

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

während mit dem darunter liegenden Feld das gleiche tun darf:

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

Gibt es eine Erklärung für dieses Verhalten?

Grüße

Antwort

27

Da „Rec“ ist eine Eigenschaft, der Compiler es ein wenig anders behandelt, weil es zunächst bewerten die „Lesen“ der Eigenschaft Decl hat. Betrachten Sie diese, was zu Ihrem Beispiel semantisch äquivalent ist:

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

Wenn man es so aussehen, können Sie sehen, dass der erste Hinweis auf „Rec“ (vor dem Punkt ‚‘), hat GetRec anrufen , wodurch eine temporäre lokale Kopie von Rec erstellt wird. Diese Provisorien sind standardmäßig "schreibgeschützt". Das ist es, worauf Sie stoßen.

Eine andere Sache, die Sie hier tun können, ist, um die einzelnen Felder des Datensatzes als Eigenschaften auf der enthaltenden Klasse ausbricht:

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

Dies ermöglicht es Ihnen, direkt auf dem Spielfeld durch die Eigenschaft zuweisen davon eingebettet Record in der Klasseninstanz.

+1

+1 Bumped in diese 4 Jahre nach der Antwort haben! –

4

Da Sie implizite Getter- und Setterfunktionen haben und das Ergebnis einer Funktion nicht ändern können, da es sich um einen const-Parameter handelt.

(Hinweis: Wenn Sie den Datensatz in einem Objekt transformieren, wäre das Ergebnis tatsächlich ein Zeiger, also äquivalent zu einem var-Parameter).

Wenn Sie bei einem Datensatz bleiben möchten, müssen Sie eine Zwischenvariable (oder die Feldvariable) verwenden oder eine WITH-Anweisung verwenden.

Siehe die unterschiedlichen Verhaltensweisen in dem folgenden Code mit den expliziten Getter und Setter-Funktionen:

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

Ja, das ist ein Problem. Aber das Problem kann gelöst werden mit Datensatz-Eigenschaften:

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; 

Dies kompiliert und funktioniert ohne Problem.

+3

+1 Beachten Sie, dass Ihre Lösung nicht schlecht ist, aber die Benutzer müssen sich daran erinnern, dass der Zuweisungstrick kläglich scheitern wird, wenn er die Eigenschaft in "property Rec: TRec liest GetRec write FRec;" Kopieren * als Datensätze sind * Werttypen *). –

+0

Die Rec-Eigenschaft in TForm1 kann nur gelesen werden, wenn nur Lese-/Schreibzugriff auf die Eigenschaften des Datensatzes erforderlich ist. Der Schlüssel zu dieser Lösung sind die Setter-Methoden in den Eigenschaften des Datensatzes. – Griffyn

8

Der Compiler verhindert die Zuweisung zu einem temporären. Das Äquivalent in C# ist zulässig, hat jedoch keine Auswirkungen. Der Rückgabewert der Rec-Eigenschaft ist eine Kopie des zugrunde liegenden Felds, und das Zuweisen zu dem Feld auf der Kopie ist ein Nop.

2

Wie andere bereits gesagt haben - die Eigenschaft read gibt eine Kopie des Datensatzes zurück, so dass die Zuweisung von Feldern nicht auf die Kopie von TForm1 wirkt.

Eine weitere Option ist so etwas wie:

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 wird dereferenzieren der PrEC Zeiger für Sie, also Dinge wie diese wird immer noch funktionieren:

Form1.Rec.A := 1234; 

Es gibt keine Notwendigkeit für einen Schreib Teil der Eigenschaft, es sei denn, Sie möchten den PRec-Puffer, auf den FRec zeigt, austauschen. Ich würde wirklich nicht vorschlagen, solche Swapping über eine Eigenschaft sowieso zu tun.

2

Der einfachste Ansatz ist:

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

Ich denke, du hast recht - es hat keinen Sinn, Eigenschaften für Datensätze zu verwenden, es scheint wie viel Arbeit ... nur eine Prozedur, die etwas zu einem Datensatz macht: SetSomething (var ARec: TRec) – sergeantKK

3

Dies liegt daran, Eigentum tatsächlich als eine Funktion erfüllt. Eigenschaften geben nur einen Wert zurück oder legen einen Wert fest. Es ist nicht ein Verweis oder ein Zeiger auf den Datensatz

so:

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

ist das gleiche wie eine Funktion wie folgt aufrufen:

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

, was Sie tun können, ist:

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

Es ist ein bisschen chaotisch, aber inhärent in dieser Art von Architektur.

7

Eine Lösung, die ich häufig verwende, ist, die Eigenschaft als Zeiger auf den Datensatz zu deklarieren.

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; 

Damit direkt Form1.Rec.A := MyInteger Zuordnung funktionieren wird, aber auch wird Form1.Rec := MyRec arbeiten, indem sie in MyRec zum FRec Feld alle Werte zu kopieren, wie erwartet.

Die einzige Gefahr ist hier, dass, wenn Sie möchten, um tatsächlich eine Kopie des Datensatzes abzurufen, mit zu arbeiten, die Sie so etwas wie MyRec := Form1.Rec^

+0

Hervorragend und professionell – Vassilis