“左側を割り当てることはできません” Delphiのレコードタイププロパティ
質問
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;
Recプロパティのメンバーのいずれかに値を割り当てようとすると、「左側を割り当てることができません」というメッセージが表示されます。エラー:
procedure TForm1.DoSomething(ARec: TRec);
begin
Rec.A := ARec.A;
end;
基礎となるフィールドで同じことを実行している間は許可されます:
procedure TForm1.DoSomething(ARec: TRec);
begin
FRec.A := ARec.A;
end;
その動作について説明はありますか?
解決
" Rec"以来はプロパティであるため、コンパイラは最初に" read"を評価する必要があるため、少し異なる方法で処理します。プロパティの宣言これを考慮してください。これは例と意味的に同等です:
...
property Rec: TRec read GetRec write FRec;
...
このように見ると、「Rec」への最初の参照がわかります。 (ドット '。'の前)、GetRecを呼び出す必要があります。これは、Recの一時的なローカルコピーを作成します。これらの一時的なデザインは、「読み取り専用」です。これはあなたが実行しているものです。
ここでできるもう1つのことは、レコードの個々のフィールドを包含クラスのプロパティとして分割することです。
...
property RecField: Integer read FRec.A write FRec.A;
...
これにより、プロパティを介してクラスインスタンスの埋め込みレコードのフィールドに直接割り当てることができます。
他のヒント
はい、これは問題です。ただし、レコードプロパティを使用して問題を解決できます。
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;
これは問題なくコンパイルして動作します。
よく使用する解決策は、プロパティをレコードへのポインタとして宣言することです。
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;
これにより、直接 Form1.Rec.A:= MyInteger
を割り当てることができますが、 Form1.Rec:= MyRec
は< code> MyRec を期待どおり FRec
フィールドに追加します。
ここでの唯一の落とし穴は、処理するレコードのコピーを実際に取得する場合、 MyRec:= Form1.Rec ^
コンパイラは、一時への割り当てを停止しています。 C#の同等のものは許可されますが、効果はありません。 Recプロパティの戻り値は基になるフィールドのコピーであり、コピー上のフィールドへの割り当てはnopです。
暗黙のゲッターおよびセッター関数があり、constパラメーターであるため関数の結果を変更できないため。
(注:オブジェクトのレコードを変換する場合、結果は実際にはポインターになります。したがって、varパラメーターに相当します)。
レコードを保持する場合は、中間変数(またはフィールド変数)を使用するか、WITHステートメントを使用する必要があります。
ゲッター関数とセッター関数を明示的に使用して、次のコードのさまざまな動作を参照してください。
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;
これは、プロパティが実際には関数としてコンパイルされているためです。プロパティは値を返すか設定するだけです。レコードへの参照またはポインタではありません
so:
Testing.TestRecord.I := 10; // error
は次のような関数を呼び出すのと同じです:
Testing.getTestRecord().I := 10; //error (i think)
できることは:
r := Testing.TestRecord; // read
r.I := 10;
Testing.TestRecord := r; //write
少し複雑ですが、このタイプのアーキテクチャに固有のものです。
他の人が言ったように-読み取りプロパティはレコードのコピーを返すので、フィールドの割り当てはTForm1が所有するコピーに作用しません。
別のオプションは次のようなものです:
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はPRecポインターを逆参照するため、このようなことは引き続き機能します。
Form1.Rec.A := 1234;
FRecが指すPRecバッファーを交換する場合を除き、プロパティの書き込み部分は必要ありません。とにかく、プロパティを介してこのようなスワッピングを行うことはお勧めしません。
最も簡単なアプローチは次のとおりです。
procedure TForm1.DoSomething(ARec: TRec);
begin
with Rec do
A := ARec.A;
end;