Delphi:类中的记录

时间:2009-12-17 08:00:47

标签: delphi delphi-2010 delphi-2007 records

以下情况:

type
  TRec = record
    Member : Integer;
  end; 

  TMyClass = class
  private
    FRec : TRec;
  public
    property Rec : TRec read FRec write FRec;
  end;

以下不起作用(左侧无法分配),这是可以的,因为TRec是值类型:

MyClass.Rec.Member := 0;

在D2007中,虽然以下内容有效:

with MyClass.Rec do
  Member := 0;

不幸的是,它在D2010中不起作用(我认为它在D2009中也不起作用)。第一个问题:为什么?故意改变了吗?或者它只是其他一些变化的副作用? D2007的解决方法只是一个“错误”吗?

第二个问题:您如何看待以下解决方法?使用安全吗?

with PRec (@MyClass.Rec)^ do
  Member := 0;

我在这里讨论的是现有代码,因此必须进行的更改才能使其有效。

谢谢!

6 个答案:

答案 0 :(得分:6)

MyClass.Rec.Member := 0;

不编译是设计的。事实上,两个“有” - 构造的编译是(AFAICT)仅仅是疏忽。所以两者都“安全使用”。

两个安全的解决方案是:

  1. MyClass.Rec分配给您操作并分配回MyClass.Rec的临时记录。
  2. TMyClass.Rec.Member作为财产展开。

答案 1 :(得分:3)

在某些类似的情节中,一个班级的记录需要“直接操纵”,我经常使用以下内容:

PMyRec = ^TMyRec;
TMyRec = record
  MyNum : integer
end;

TMyObject = class( TObject )
PRIVATE
  FMyRec : TMyRec;
  function GetMyRec : PMyRec;
PUBLIC
  property MyRec : PMyRec << note the 'P'
    read GetMyRec;
end;

function TMyObject.GetMyRec : PMyRec; << note the 'P'
begin
  Result := @FMyRec;
end;

这样做的好处是,您可以利用Delphi自动解除引用来对每个记录元素进行可读代码访问,即:

MyObject.MyRec.MyNum:= 123;

我无法记住,但也许WITH可以使用这种方法 - 我尽量不使用它! 布赖恩

答案 2 :(得分:1)

无法直接分配的原因是here 对于WITH,它仍然可以在D2009中使用,我本来期望它也可以在D2010中工作(我现在无法测试)。
更安全的方法是直接暴露记录属性,正如艾伦在上面提到的那样SO post

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

答案 3 :(得分:0)

记录是,它们并不是实体。

他们甚至具有逐个复制的语义!这就是为什么你不能就地改变财产价值的原因。因为它会违反FRec的值类型语义,并且依赖于它的中断代码是不可变的或至少是安全的副本。

他们在这里提出的问题是,为什么你需要一个值(你的TRec)来表现得像一个对象/实体?

如果这是你正在使用它的那个“TRec”不是更合适吗?

我的观点是,当你开始使用超出其意图的语言功能时,你可以很容易地发现自己处于每个米路上必须对抗你的工具的情况。

答案 4 :(得分:0)

它被改变的原因是它是一个编译器错误。它编译的事实并不能保证它能够正常工作。 一旦将Getter添加到属性

,它就会失败
unit Unit2;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls;

type
  TForm2 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    FPoint: TPoint;
    function GetPoint: TPoint;
    procedure SetPoint(const Value: TPoint);
    { Private declarations }
  public
    { Public declarations }
    property Point : TPoint read GetPoint write SetPoint;
  end;

var
  Form2: TForm2;

implementation

{$R *.dfm}

procedure TForm2.Button1Click(Sender: TObject);
begin
  with Point do
  begin
    X := 10;
    showmessage(IntToStr(x)); // 10
  end;

  with Point do
    showmessage(IntToStr(x)); // 0

  showmessage(IntToStr(point.x)); // 0
end;

function TForm2.GetPoint: TPoint;
begin
  Result := FPoint;
end;

procedure TForm2.SetPoint(const Value: TPoint);
begin
  FPoint := Value;
end;

end.

你的代码会突然中断,你会责备Delphi / Borland首先允许它。

如果你不能直接分配一个属性,不要使用hack来分配它 - 它会在某一天咬回来。

使用Brian的建议返回一个指针,但放弃With - 你可以直接做Point.X:= 10;

答案 5 :(得分:-2)

另一种解决方案是使用辅助函数:

procedure SetValue(i: Integer; const Value: Integer);
begin
  i := Value;
end;
SetValue(MyClass.Rec.Member, 10);

虽然它仍然不安全(参见Barry Kelly关于Getter / Setter的评论)

/编辑:下面是最丑陋的黑客(也可能是最不安全的),但我发布它真是太好笑了:

type
  TRec = record
    Member : Integer;
    Member2 : Integer;
  end;

  TMyClass = class
  private
    FRec : TRec;
    function GetRecByPointer(Index: Integer): Integer;
    procedure SetRecByPointer(Index: Integer; const Value: Integer);
  public
    property Rec : TRec read FRec write FRec;
    property RecByPointer[Index: Integer] : Integer read GetRecByPointer write SetRecByPointer;
  end;

function TMyClass.GetRecByPointer(Index: Integer): Integer;
begin
  Result := PInteger(Integer(@FRec) + Index * sizeof(PInteger))^;
end;

procedure TMyClass.SetRecByPointer(Index: Integer; const Value: Integer);
begin
  PInteger(Integer(@FRec) + Index * sizeof(PInteger))^ := Value;
end;

它假设记录的每个成员都是(P)整数大小,如果没有则会崩溃。

  MyClass.RecByPointer[0] := 10;  // Set Member
  MyClass.RecByPointer[1] := 11;  // Set Member2

您甚至可以将偏移硬编码为常量并直接通过偏移

进行访问
const
  Member = 0;
  Member2 = Member + sizeof(Integer);  // use type of previous member

  MyClass.RecByPointer[Member] := 10;

    function TMyClass.GetRecByPointer(Index: Integer): Integer;
    begin
      Result := PInteger(Integer(@FRec) + Index)^;
    end;

    procedure TMyClass.SetRecByPointer(Index: Integer; const Value: Integer);
    begin
      PInteger(Integer(@FRec) + Index)^ := Value;
    end;

MyClass.RecByPointer[Member1] := 20;