für Satzart Eigenschaften in Delphi „Linke Seite kann nicht zugewiesen werden“

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

  •  05-07-2019
  •  | 
  •  

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?

War es hilfreich?

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.

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;

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;
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top