für Satzart Eigenschaften in Delphi „Linke Seite kann nicht zugewiesen werden“
Frage
Ich bin neugierig zu wissen, warum Delphi behandelt Eigenschaften Satzart als nur lesen:
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 der Mitglieder der Rec Eigenschaft zuweisen, ich werde Fehler „Linke Seite kann nicht zugewiesen werden“:
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?
Lösung
Da „Rec“ eine Eigenschaft ist, behandelt der Compiler es ein wenig anders, weil es das „Lesen“ der Eigenschaft Decl ersten bewerten muss. Betrachten Sie diese, die semantisch äquivalent zu Ihrem Beispiel 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 zu nennen, die eine temporäre lokale Kopie Rec schaffen. Diese Provisorien sind durch Design "read-only". Dies ist, was Sie laufen in.
Eine andere Sache, die Sie hier tun können, ist, die einzelnen Felder des Datensatzes als Eigenschaften auf der enthaltenden Klasse auszubrechen:
...
property RecField: Integer read FRec.A write FRec.A;
...
Damit können Sie direkt auf das Gebiet des eingebetteten Datensatzes in der Klasseninstanz über die Eigenschaft zuweisen.
Andere Tipps
Ja, das ist ein Problem. Aber das Problem kann Rekord Eigenschaften gelöst werden mit:
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;
Dies kompiliert und arbeitet ohne Problem.
Eine Lösung, die ich häufig verwenden ist die Eigenschaft als Zeiger auf den Datensatz zu erklären.
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 zuweisen Form1.Rec.A := MyInteger
funktionieren wird, sondern auch, indem Sie alle Werte in Form1.Rec := MyRec
zum MyRec
Feld wie erwartet FRec
funktionieren wird.
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^
haben
Der Compiler Sie stoppt in einem temporären aus zuweisen. Das Äquivalent in C # ist zulässig, aber es hat keine Wirkung; der Rückgabewert der Rec-Eigenschaft ist eine Kopie des zugrunde liegenden Feldes und auf der Kopie auf das Feld Zuordnung ist ein nop.
Weil Sie implizite Getter und Setter-Funktionen haben, und Sie können das Ergebnis einer Funktion nicht ändern, da es ein const-Parameter ist.
. (Hinweis: Falls Sie den Datensatz in einem Objekt verwandeln, wäre das Ergebnis tatsächlich ein Zeiger sein, so entspricht einen var-Parameter)
Wenn Sie mit einem Rekord bleiben wollen, müssen Sie eine Zwischengröße verwenden (oder die Feldgröße) oder eine Verwendung mit Aussage.
Siehedie 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;
Das ist, weil Eigenschaft tatsächlich als eine Funktion erfüllt. Eigenschaften nur zurückgeben oder einen Wert gesetzt. Es ist nicht ein Verweis oder ein Zeiger auf den Datensatz
so:
Testing.TestRecord.I := 10; // error
ist gleich 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 das in dieser Art von Architektur.
Wie schon andere gesagt haben -. Die Lese Eigenschaft eine Kopie des Datensatz zurück, so dass die Zuordnung von Feldern handelt nicht auf der Kopie im Besitz von TForm1
Eine andere Möglichkeit 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 Immobilie, wenn Sie bei der PrEC Puffer, frec Punkte tauschen wollen. Ich würde wirklich nicht den Eindruck erwecken solchen Swapping über eine Eigenschaft auf jeden Fall zu tun.
Der einfachste Ansatz ist:
procedure TForm1.DoSomething(ARec: TRec);
begin
with Rec do
A := ARec.A;
end;