“左侧不能分配给” Delphi 中的记录类型属性

发布于 2024-07-14 20:19:40 字数 560 浏览 6 评论 0原文

我很好奇为什么 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;

对该行为有任何解释吗?

I'm curious to know why Delphi treats record type properties as read only:

  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;

If I try to assign a value to any of the members of Rec property, I'll get "Left side cannot be assigned to" error:

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

while doing the same with the underlying field is allowed:

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

Is there any explanation for that behavior?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(8

与酒说心事 2024-07-21 20:19:40

由于“Rec”是一个属性,编译器对它的处理方式略有不同,因为它必须首先评估属性 decl 的“读取”。 考虑一下这一点,这在语义上等同于您的示例:

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

如果您像这样查看它,您可以看到对“Rec”的第一个引用(在点“.”之前)必须调用 GetRec,这将创建一个临时本地Rec 的副本。 这些临时对象被设计为“只读”。 这就是你遇到的情况。

您可以在此处执行的另一件事是将记录的各个字段分解为包含类的属性:

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

这将允许您通过属性直接分配给类实例中嵌入记录的字段。

Since "Rec" is a property, the compiler treats it a little differently because it has to first evaluate the "read" of the property decl. Consider this, which is semantically equivalent to your example:

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

If you look at it like this, you can see that the first reference to "Rec" (before the dot '.'), has to call GetRec, which will create a temporary local copy of Rec. These temporaries are by design "read-only." This is what you're running into.

Another thing you can do here is to break out the individual fields of the record as properties on the containing class:

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

This will allow you to directly assign through the property to the field of that embedded record in the class instance.

樱桃奶球 2024-07-21 20:19:40

是的,这是一个问题。 但问题可以使用记录属性来解决:

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;

这可以毫无问题地编译和工作。

Yes this is a problem. But the problem can be solved using record properties:

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;

This compiles and workes without problem.

游魂 2024-07-21 20:19:40

我经常使用的解决方案是将属性声明为指向记录的指针。

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 也可以通过复制 MyRec 中的所有值来工作 按预期添加到 FRec 字段。

这里唯一的陷阱是,当您希望实际检索要使用的记录的副本时,您将不得不使用类似 MyRec := Form1.Rec^

A solution I frequently use is to declare the property as a pointer to the record.

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;

With this, directly assigning Form1.Rec.A := MyInteger will work, but also Form1.Rec := MyRec will work by copying all the values in MyRec to the FRec field as expected.

The only pitfall here is that when you wish to actually retrieve a copy of the record to work with, you will have to something like MyRec := Form1.Rec^

阪姬 2024-07-21 20:19:40

编译器阻止您分配给临时变量。 C# 中的等效项是允许的,但没有效果; Rec 属性的返回值是基础字段的副本,并且分配给副本上的字段是 nop。

The compiler is stopping you from assigning to a temporary. The equivalent in C# is permitted, but it has no effect; the return value of the Rec property is a copy of the underlying field, and assigning to the field on the copy is a nop.

许仙没带伞 2024-07-21 20:19:40

因为您有隐式 getter 和 setter 函数,并且您无法修改函数的 Result,因为它是 const 参数。

(注意:如果您转换对象中的记录,结果实际上是一个指针,因此相当于 var 参数)。

如果您想保留记录,则必须使用中间变量(或字段变量)或使用WITH 语句。

使用显式 getter 和 setter 函数查看以下代码中的不同行为:

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;

Because you have implicit getter and setter functions and you cannot modify the Result of a function as it is a const parameter.

(Note: In case you transform the record in an object, the result would actually be a pointer, thus equivalent to a var parameter).

If you want to stay with a Record, you have to use an intermediate variable (or the Field variable) or use a WITH statement.

See the different behaviors in the following code with the explicit getter and setter functions:

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;
要走干脆点 2024-07-21 20:19:40

最简单的方法是:

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

The simplest approach is:

procedure TForm1.DoSomething(ARec: TRec);
begin
  with Rec do
    A := ARec.A;
end;
错々过的事 2024-07-21 20:19:40

这是因为属性实际上是作为函数编译的。 属性仅返回或设置一个值。 它不是指向记录的引用或指针

,因此:

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

与调用这样的函数相同:

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

您可以做的是:

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

它有点混乱,但是是这种类型的体系结构所固有的。

This is because property are actually complied as a function. Properties only return or set a value. It is not a reference or a pointer to the record

so :

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

is same as calling a function like this:

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

what you can do is:

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

It is a bit messy but inherent in this type of architecture.

说不完的你爱 2024-07-21 20:19:40

正如其他人所说 - 读取属性将返回记录的副本,因此字段的分配不会作用于 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 缓冲区。 无论如何,我真的不建议通过属性进行此类交换。

Like others have said - the read property will return a copy of the record, so the assignment of fields isn't acting on the copy owned by TForm1.

Another option is something like:

  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 will dereference the PRec pointer for you, so things like this will still work:

Form1.Rec.A := 1234; 

There's no need for a write part of the property, unless you want to swap the PRec buffer that FRec points at. I really wouldn't suggest to do such swapping via a property anyway.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文