Delphi,记录类型 属性,记录字段分配:分配给预期记录的本地副本

Delphi, record type property, record field assignment: Assignment to local copy of record expected

关于 Toon Krijthe 的问题 “Left side cannot be assigned to” for record type properties in Delphi, there is an answer,演示了如何通过在记录声明中使用属性来完成对记录 属性 字段的赋值。为了便于参考,这里是 Toon Krijthe 发布的代码片段。

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;

我很清楚为什么vcldeveloper的原始代码中出现"Left side cannot be assigned to"错误而记录中没有setter。我也很清楚,如果像上面的代码一样为 属性 TRec.A 定义了 setter,那么为什么赋值 Rec.A := 21; 不会出现错误。

我不明白的是为什么赋值Rec.A := 21;将值21赋值给TForm1的字段FRec.FA。我本以为该值被分配给 FRec 本地临时副本的字段 FA 而不是 FRec.FA 本身。任何人都可以阐明这里发生的事情吗?

这是一个很好的问题!

您看到的行为是属性实现细节的结果。编译器实现属性的方式对于直接字段 属性 getters 和函数 属性 getters.

是不同的

写的时候

Rec.A := 21;

编译器看到Rec并知道它是一个属性。由于 getter 是直接字段 getter,编译器只需将 Rec 替换为 FRec 并编译代码,就好像您编写了

FRec.A := 21;

然后编译器遇到A属性并使用setter方法,所以你的赋值变成

FRec.SetA(21);

因此您观察到的行为。

假设您有一个函数 getter

而不是直接字段 getter
property Rec: TRec read GetRec;
....
function TForm1.GetRec: TRec;
begin
  Result := FRec;
end;

在那种情况下

的处理
Rec.A := 21;

变化。编译器改为声明一个隐式局部变量,代码编译如下:

var
  __local_rec: TRec;
....
__local_rec := GetRec;
__local_rec.A := 21;

在我看来很明显,这样一个程序的行为不应该取决于 属性 getter 是直接字段 getter 还是函数 getter .这似乎是 属性 功能与增强记录功能之间交互的设计缺陷。


这是一个非常简洁地演示问题的完整程序:

{$APPTYPE CONSOLE}

type
  TRec = record
  private
    FA: Integer;
    procedure SetA(const Value: integer);
  public
    property A: integer read FA write SetA;
  end;

procedure TRec.SetA(const Value: integer);
begin
  FA := Value;
end;

type
  TMyClass = class
  private
    FRec: TRec;
    function GetRec: TRec;
  public
    property RecDirect: TRec read FRec;
    property RecFunction: TRec read GetRec;
  end;

var
  Obj: TMyClass;

function TMyClass.GetRec: TRec;
begin
  Result := FRec;
end;

begin
  Obj := TMyClass.Create;
  Obj.RecDirect.A := 21;
  Writeln(Obj.FRec.FA);

  Obj := TMyClass.Create;
  Obj.RecFunction.A := 21;
  Writeln(Obj.FRec.FA);
end.

输出

21
0