“左側を割り当てることはできません” Delphiのレコードタイププロパティ

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

  •  05-07-2019
  •  | 
  •  

質問

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;
ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top