我很想知道为什么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;
这种行为有什么解释吗?
答案 0 :(得分:34)
由于“Rec”是一个属性,编译器对它的处理方式略有不同,因为它必须首先评估属性decl的“读取”。考虑一下,这在语义上等同于您的示例:
...
property Rec: TRec read GetRec write FRec;
...
如果您这样看,您可以看到第一次引用“Rec”(在点'。'之前),必须调用GetRec,这将创建Rec的临时本地副本。这些临时设计在设计上是“只读的”。这就是你遇到的。
您可以在这里做的另一件事是将记录的各个字段分解为包含类的属性:
...
property RecField: Integer read FRec.A write FRec.A;
...
这将允许您通过属性直接分配给类实例中该嵌入记录的字段。
答案 1 :(得分:19)
是的,这是一个问题。但问题可以通过记录属性来解决:
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;
这可以毫无问题地编译和工作。
答案 2 :(得分:10)
我经常使用的解决方案是将属性声明为指向记录的指针。
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^
答案 3 :(得分:7)
编译器阻止您分配临时。允许使用C#中的等价物,但它没有效果; Rec属性的返回值是基础字段的副本,并且分配给副本上的字段是一个nop。
答案 4 :(得分:4)
因为你有隐式的getter和setter函数,你不能修改函数的Result,因为它是一个const参数。
(注意:如果你在一个对象中转换记录,结果实际上是一个指针,因此相当于一个var参数)。
如果要保留Record,则必须使用中间变量(或Field变量)或使用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;
答案 5 :(得分:3)
这是因为属性实际上被编译为函数。属性仅返回或设置值。它不是引用或指向记录的指针
所以:
Testing.TestRecord.I := 10; // error
与调用这样的函数相同:
Testing.getTestRecord().I := 10; //error (i think)
你能做的是:
r := Testing.TestRecord; // read
r.I := 10;
Testing.TestRecord := r; //write
这种架构有点混乱但是固有的。
答案 6 :(得分:2)
就像其他人所说的那样 - read属性将返回记录的副本,因此字段的分配不会对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缓冲区,否则不需要属性的写入部分。无论如何,我真的不建议通过房产进行这种交换。
答案 7 :(得分:2)
最简单的方法是:
procedure TForm1.DoSomething(ARec: TRec);
begin
with Rec do
A := ARec.A;
end;