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

Delphi 记录可以用作对象的属性吗?,delphi,properties,delphi-xe2,Delphi,Properties,Delphi Xe2,我想做一个记录作为对象的属性。问题是,当我更改此记录的一个字段时,对象没有意识到更改 type TMyRecord = record SomeField: Integer; end; TMyObject = class(TObject) private FSomeRecord: TMyRecord; procedure SetSomeRecord(const Value: TMyRecord); public property SomeRec

我想做一个记录作为对象的属性。问题是,当我更改此记录的一个字段时,对象没有意识到更改

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;
如果我这样做了

MyObject.SomeRecord.SomeField:= 5;
…行不通

那么,当记录的某个字段被写入时,如何使属性设置过程“catch”?也许是关于如何申报记录的一些技巧

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;
更多信息


我的目标是避免使用
OnChange
事件(例如
TFont
TStringList
)创建一个
TObject
t持久的
)。我非常熟悉使用对象来实现这一点,但为了稍微清理一下代码,我正在考虑是否可以使用记录来代替。我只需要确保在设置记录的一个字段时可以正确调用我的记录属性setter。

您正在按值传递记录,因此对象存储了记录的副本。从那时起,实际上有两个目标;原件和由该对象持有的副本。改变一个不会改变另一个

您需要通过引用传递记录

type
  TMyRecord = record
    SomeField: Integer;
  end;
  PMyRecord = ^TMyRecord;

  TMyObject = class(TObject)
  private
    FSomeRecord: PMyRecord;
    procedure SetSomeRecord(const Value: PMyRecord);
  public
    property SomeRecord: PMyRecord read FSomeRecord write SetSomeRecord;
  end;

用TObject代替记录怎么样

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;
现在,您可以像下面这样读取和设置TMYProperty的属性:

MyObject.MyProperties.SomeField := 1;
x := MyObject.MyProperties.SomeField;
MyRecord := MyObject.SomeRecord;
MyRecord.SomeField := NewValue;
使用此方法,不需要单独获取/设置记录中的值。如果需要捕获FmyProperty中的更改,可以在属性声明中添加“set”过程。

考虑这一行:

MyObject.SomeRecord.SomeField := NewValue;
这实际上是一个编译错误:

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

您的实际代码可能如下所示:

MyObject.MyProperties.SomeField := 1;
x := MyObject.MyProperties.SomeField;
MyRecord := MyObject.SomeRecord;
MyRecord.SomeField := NewValue;
这里发生的是将记录类型的复制到局部变量
MyRecord
。然后修改此本地副本的字段。这不会修改MyObject中保存的记录。为此,需要调用属性设置器

MyRecord := MyObject.SomeRecord;
MyRecord.SomeField := NewValue;
MyObject.SomeRecord := MyRecord;
或者切换到使用引用类型,即类,而不是记录


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

,最终您会希望访问记录的字段,但正如您所建议的,记录通常是类中合适的抽象选择。类可以灵活地访问记录的属性,如下所示:

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;
类型
TMyRec=记录
SomeReceinter:整数;
SomeRecString:string;
结束;
TMyClass=类(TObject)
私有的
FMyRec:TMyRec;
过程SetSomeString(常数:string);
公众的
属性SomeInteger:整数读取FMyRec.someReceinter写入FMyRec.someReceinter;
属性SomeString:string read FMyRec.SomeRecString write SetSomeString;
结束;
过程TMyClass.SetSomeRecString(常数:字符串);
开始
如果你想要什么东西的话
开始
//如果设置了SomeRecString属性,请执行一些特殊操作
FMyRec.SomeRecString:=AString;
结束;
结束;
注:

  • 直接访问记录属性SomeReceinteger
  • 使用SetSomeRecString仅对该字段执行某些特殊处理

  • 希望这能有所帮助。

    为什么不让二传手/接球手成为记录的一部分呢

    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;
    

    这是@SourceMaid答案的另一种选择

    您可以在对象内部使用记录(而不是指向记录的指针),并具有一个只读属性,该属性返回指向记录的指针

    班级:

    type
      TMyRecord = record
        I:Integer;
      end;
      PMyRecord = ^TMyRecord;
      TMyObject = class
      private
        FMyRecord:TMyRecord;
      function GetMyRecordPointer: PMyRecord;
      public
        property MyRecord: PMyRecord read GetMyRecordPointer;
      end;
    
    吸气剂:

    function TMyObject.GetMyRecordPointer: PMyRecord;
    begin
      result := @FMyRecord;
    end;
    
    用法:

    o := TMyObject.Create;
    o.MyRecord.I := 42;
    ShowMessage(o.MyRecord.I.ToString);
    o.MyRecord.I := 23;
    ShowMessage(o.MyRecord.I.ToString);
    o.Free;
    
    你不需要一个设定者,因为你得到了一个推荐人并与之合作。这意味着您不能通过分配新记录来更改整个记录。

    但是,您可以直接操作记录的元素,而不会出现错误
    “Left side cannot assignment to”

    。这使得类的使用非常困难,因为您强制类的客户端分配记录,并确保其生存期跟踪对象的生存期。当然,您的意思是将记录存储在类中,并公开用getter函数实现的只读指针属性。结果实现:=@FSomeRecord,其中FSomeRecord是TMyRecord。如果要这样做,基本上相当于将记录声明为类的公共字段,您也可以这样做,因为这样更容易理解。@DavidHeffernan同意,这只会带来更多的困难。+1为了方便起见,我还将添加一个接受TMyRec参数的过程,这样我就可以一次性设置所有值。+1不完全是我想要的,但教会了我一些新的东西:我从来都不知道可以使用记录的字段作为属性getter/setter。事实上,我经常使用对象(和persistents)。它通常还包括一些您没有的内容:OnChange事件。本质上,我的重点是能够捕获此记录(或对象)的更改在
    SetSomeRecord
    过程中。我想看看是否可以将其封装到一个记录中,而不需要
    OnChange
    事件。这是否是
    packed记录
    可能派上用场的地方?
    packed
    或其他方面是无关紧要的。这只是控制记录的布局和对齐。fundamental的问题是值类型和引用类型之间的区别。事实上,你问题中的代码甚至都没有编译。@DavidHeffernan非常正确,我实际上没有把代码放在Delphi中,而是直接在这里键入的。+1这意味着绝对没有办法让记录属性设置程序捕获对其字段的修改?糟糕er:(对于基于对象的属性也是如此,这就是为什么像
    TFont
    TStrings
    等类有一个
    OnChange
    事件-父组件为该事件分配一个内部事件处理程序,这样子属性的更改就可以被处理