Delphi:类中的记录

发布于 2024-08-15 17:52:32 字数 662 浏览 3 评论 0原文

以下情况:

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 解决方法只是一个“bug”吗?

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

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

我在这里讨论的是现有代码,因此要使其工作而必须进行的更改应该是最小的。

谢谢!

Following situation:

type
  TRec = record
    Member : Integer;
  end; 

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

The following doesn't work (left side cannot be assigned to), which is okay since TRec is a value type:

MyClass.Rec.Member := 0;

In D2007 though the following DOES work:

with MyClass.Rec do
  Member := 0;

Unfortunately, it doesn't work in D2010 (and I assume that it doesn't work in D2009 either). First question: why is that? Has it been changed intentionally? Or is it just a side effect of some other change? Was the D2007 workaround just a "bug"?

Second question: what do you think of the following workaround? Is it safe to use?

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

I'm talking about existing code here, so the changes that have to be made to make it work should be minimal.

Thanks!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

霞映澄塘 2024-08-22 17:52:32

MyClass.Rec.Member := 0;

编译是设计使然。事实上,两个“with”结构的编译(AFAICT)仅仅是一个疏忽。因此,两者都“可以安全使用”。

两个安全的解决方案是:

  1. MyClass.Rec 分配给您操作的临时记录,然后分配回 MyClass.Rec
  2. TMyClass.Rec.Member 公开为其自身的属性。

That

MyClass.Rec.Member := 0;

doesn't compile is by design. The fact that the both "with"-constructs ever compiled was (AFAICT) a mere oversight. So both are not "safe to use".

Two safe solution are:

  1. Assign MyClass.Rec to a temporary record which you manipulate and assign back to MyClass.Rec.
  2. Expose TMyClass.Rec.Member as a property on its own right.
你是我的挚爱i 2024-08-22 17:52:32

在像这样的情况下,类的记录需要“直接操作”,我经常采用以下方法:

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适用于这种方法——我尽量不使用它!
布莱恩

In some situtations like this where a record of a class needs 'direct manipulation' I've often resorted to the following:

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;

The benefit of this is that you can leverage the Delphi automatic dereferencing to make readable code access to each record element viz:

MyObject.MyRec.MyNum := 123;

I cant remember, but maybe WITH works with this method - I try not to use it!
Brian

我最亲爱的 2024-08-22 17:52:32

不能直接赋值的原因是这里
至于WITH,它仍然可以在D2009中工作,我希望它也可以在D2010中工作(我现在无法测试)。
更安全的方法是直接公开记录属性,如 Allen 在上述 SO 帖子

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

The reason why it can't be directly assigned is here.
As for the WITH, it still works in D2009 and I would have expected it to work also in D2010 (which I can't test right now).
The safer approach is exposing the record property directly as Allen suggesed in the above SO post:

property RecField: Integer read FRec.A write FRec.A;
眼眸印温柔 2024-08-22 17:52:32

记录是,它们并不意味着是实体。

它们甚至具有按副本分配的语义!这就是为什么您无法就地更改属性值的原因。因为它会违反 FRec 的值类型语义,并破坏依赖它不可变或至少是安全副本的代码。

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

无论如何,如果这就是您使用它的目的,那么将“TRec”作为一个类不是更合适吗?

我的观点是,当你开始使用超出其意图的语言功能时,你很容易发现自己处于必须一路与你的工具作斗争的境地。

Records are values, they aren't meant to be entities.

They even have assignment-by-copy semantics! Which is why you can't change the property value in-place. Because it would violate the value type semantics of FRec and break code that relied on it being immutable or at least a safe copy.

They question here is, why do you need a value (your TRec) to behave like an object/entity?

Wouldn't it be much more appropriate for "TRec" to be a class if that is what you are using it for, anyways?

My point is, when you start using a language feature beyond its intent, you can easily find yourself in a situation where you have to fight your tools every meter on the way.

如果没结果 2024-08-22 17:52:32

它被更改的原因是它是一个编译器错误。它编译的事实并不能保证它会起作用。
一旦将 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;

The reason it has been changed is that it was a compiler bug. The fact that it compiled didn't guarantee that it would work.
It would fail as soon as a Getter was added to the property

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.

You code would suddenly break, and you'd blame Delphi/Borland for allowing it in the first place.

If you can't directly assign a property, don't use a hack to assign it - it will bite back someday.

Use Brian's suggestion to return a pointer, but drop the With - you can eaisly do Point.X := 10;

时光匆匆的小流年 2024-08-22 17:52:32

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

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)整数大小,如果不是,AV 将崩溃。

  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;

Another solution is to use a helper function:

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

It's still not safe though (see Barry Kelly's comment about Getter/Setter)

/Edit: Below follows the most ugly hack (and probably the most unsafe as well) but it was so funny I had to post it:

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;

It assumes that every member of the record is (P)Integer sized and will crash of AV if not.

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

You could even hardcode the offsets as constants and access directly by offset

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;
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文