记录可以用作对象的属性吗?

Jer*_*dge 11 delphi properties delphi-xe2

我想把记录作为对象的属性.问题是,当我更改此记录的其中一个字段时,该对象不知道该更改.

type
  TMyRecord = record
    SomeField: Integer;
  end;

  TMyObject = class(TObject)
  private
    FSomeRecord: TMyRecord;
    procedure SetSomeRecord(const Value: TMyRecord);
  public
    property SomeRecord: TMyRecord read FSomeRecord write SetSomeRecord;
  end;
Run Code Online (Sandbox Code Playgroud)

如果我这样做......

MyObject.SomeRecord.SomeField:= 5;
Run Code Online (Sandbox Code Playgroud)

...不管用.

那么当记录其中一个记录的字段时,如何使属性设置过程'catch'?也许在如何申报记录方面有一些诀窍?

更多信息

我的目标是避免创建一个TObjectTPersistent一个OnChange事件(例如TFontTStringList).我非常熟悉为此使用对象,但是为了尝试清理我的代码,我看到我是否可以使用Record.我只需要确保在设置其中一个记录字段时可以正确调用我的记录属性设置器.

Dav*_*nan 11

考虑这一行:

MyObject.SomeRecord.SomeField := NewValue;
Run Code Online (Sandbox Code Playgroud)

这实际上是一个编译错误:

[DCC错误]:E2064左侧无法分配

你的实际代码可能是这样的:

MyRecord := MyObject.SomeRecord;
MyRecord.SomeField := NewValue;
Run Code Online (Sandbox Code Playgroud)

这里发生的是您将记录类型的复制到局部变量MyRecord.然后,您可以修改此本地副本的字段.这不会修改MyObject中保存的记录.为此,您需要调用属性设置器.

MyRecord := MyObject.SomeRecord;
MyRecord.SomeField := NewValue;
MyObject.SomeRecord := MyRecord;
Run Code Online (Sandbox Code Playgroud)

或者切换到使用引用类型,即类,而不是记录.

总而言之,您当前代码的问题是没有调用SetSomeRecord,而是您正在修改记录的副本.这是因为记录是值类型而不是引用类型.

  • 对于基于对象的属性也是如此,这就是为什么像“TFont”、“TStrings”等类具有“OnChange”事件的原因 - 父组件为该事件分配一个内部事件处理程序,因此可以对子属性进行更改传播到父组件,因为在这种情况下不会调用其属性设置器。 (2认同)
  • 一个稍微有点hacky的替代方案是拥有一个指针属性,并带有一个返回@FSomeRecord的getter。FSomeRecord的生命周期仍然由TMyObject控制,因此内存管理不存在问题。 (2认同)

Bri*_*ost 11

最终,您将需要访问记录的字段,但正如您所建议的那样,记录通常是类中适合的抽象选择.类可以整齐地访问记录的属性,如下所示:

type
  TMyRec = record
    SomeRecInteger: integer;
    SomeRecString: string;
  end;

  TMyClass = class(TObject)
  private
    FMyRec: TMyRec;
    procedure SetSomeString(const AString: string);
  public
    property SomeInteger: integer read FMyRec.SomeRecInteger write FMyRec.SomeRecInteger;
    property SomeString: string read FMyRec.SomeRecString write SetSomeString;
  end;

procedure TMyClass.SetSomeRecString(const AString: string);
begin
  If AString <> SomeString then
  begin
    // do something special if SomeRecString property is set
    FMyRec.SomeRecString := AString;
  end;
end;
Run Code Online (Sandbox Code Playgroud)

注意:

  1. 直接访问记录属性SomeRecInteger
  2. 使用SetSomeRecString仅对此字段实现一些特殊处理.

希望这可以帮助.


nor*_*aul 6

使用 TObject 代替 Record 怎么样?

type
  TMyProperties = class(TObject)
    SomeField: Integer;
  end;

  TMyObject = class(TObject)
  private
    FMyProperties: TMyProperties;     
  public
    constructor Create;
    destructor Destroy; override;

    property MyProperties: TMyRecord read FMyProperties;
  end;

implementation

constructor TMyObject.Create;
begin
  FMyProperties := TMyProperties.Create;
end;

destructor TMyObject.Destroy;
begin
  FMyProperties.Free;
end;
Run Code Online (Sandbox Code Playgroud)

您现在可以像这样读取和设置 TMyProperties 的属性:

MyObject.MyProperties.SomeField := 1;
x := MyObject.MyProperties.SomeField;
Run Code Online (Sandbox Code Playgroud)

使用此方法,您不需要单独从记录获取值或设置值。如果您需要捕获 FMyProperties 中的更改,您可以在属性声明中添加“设置”过程。


Joh*_*ley 5

为什么不让 setter/getter 成为记录的一部分呢?

TMyRecord = record
  fFirstname: string;
  procedure SetFirstName(AValue: String);
property
  Firstname : string read fFirstname write SetFirstName;
end;

TMyClass = class(TObject)
  MyRecord : TMyRecord;
end;

procedure TMyRecord.SetFirstName(AValue: String);
begin
  // do extra checking here
  fFirstname := AValue;
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  MyClass: TMyClass;
begin
  MyClass := TMyClass.Create;
  try
    MyClass.MyRecord.Firstname := 'John';
    showmessage(MyClass.MyRecord.Firstname);
  finally
    MyClass.Free;
  end;
end;
Run Code Online (Sandbox Code Playgroud)