Delphi 德尔菲:课堂记录

Delphi 德尔菲:课堂记录,delphi,delphi-2010,delphi-2007,records,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中,以下操作确实有

以下情况:

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;
我在这里谈论的是现有的代码,因此为了使其正常工作而必须进行的更改应该是最小的

谢谢

MyClass.Rec.Member := 0;
不编译是出于设计。曾经编译过的两个“with”构造(AFAICT)只是一个疏忽。因此,两者都不是“安全使用”

两种安全解决方案是:

  • MyClass.Rec
    分配给临时记录,然后将其重新分配给
    MyClass.Rec
  • TMyClass.Rec.Member
    本身作为属性公开

  • 无法直接分配的原因是。
    至于WITH,它在D2009中仍能工作,我本以为它在D2010中也能工作(我现在无法测试)。
    更安全的方法是直接公开记录属性,正如Allen在上文中所建议的那样,因此:


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

    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;
    
    PMyRec=^TMyRec;
    TMyRec=记录
    MyNum:整数
    结束;
    TMyObject=类(ToObject)
    私有的
    FMyRec:TMyRec;
    函数GetMyRec:PMyRec;
    公开的
    
    属性MyRec:PMyRec记录是,它们不是实体

    他们甚至有复制语义赋值!这就是为什么不能就地更改属性值。因为它会违反FRec的值类型语义,并破坏依赖于它的代码是不可变的或至少是安全副本

    这里的问题是,为什么您需要一个值(您的TRec)来表现为一个对象/实体

    如果“TRec”正是您使用它的目的,那么它是否更适合作为一个类呢


    我的观点是,当您开始使用一种超出其意图的语言功能时,您很容易发现自己处于这样一种情况,即您必须在每一米的路程中使用您的工具。

    另一种解决方案是使用辅助功能:

    procedure SetValue(i: Integer; const Value: Integer);
    begin
      i := Value;
    end;
    SetValue(MyClass.Rec.Member, 10);
    
    但这仍然不安全(见巴里·凯利关于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;
    

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

    如果你不能直接分配一个属性,不要使用黑客来分配它-总有一天它会回击你


    使用Brian的建议返回一个指针,但删除带-you can eaisly do Point.X:=10

    谢谢!这就是我害怕的。。。您的第二个解决方案意味着大量的工作,基本上消除了在这里使用记录的优势。第一个解决方案当然会奏效。我得想一想。另一个问题:为什么我的问题中的变通方法会奏效?我希望属性在这里返回记录的副本…解决方法之所以有效,是因为您正在对类型系统进行黑客攻击。类型系统试图阻止您写入属性,因为将来的更改可能意味着属性返回一个副本(例如getter的返回值),而不是基础字段。类型系统试图阻止它编译,但一旦该检查被忽略,代码生成器执行明显的实现,并简单地用底层的
    FRef
    字段替换对
    Ref
    属性的引用。你的新工作环境也不应该真正起作用;我非常确定您不应该能够获取指向属性的指针。MyClass.Rec.Member:=0;不接受,因为Rec是属性,而不是因为它是值类型。直接尝试使用字段,也是一种值类型,它可以工作:MyClass.FRec.Member:=0;嗯,如果
    TRec
    是一个类,它就会工作。因此,这两个事实在这里都很重要(事实上它是一个值类型,而且它是通过属性访问的),解决方法使用起来不安全。考虑对ReC属性的未来更改,这样它可以从一个GETER而不是一个字段中读取:您的HACK意味着它将修改一个临时的,并且不会对基础字段产生影响。这就是为什么属性不允许修改返回值类型的原因。除了问题but:不需要with语句,这应该以相同的方式工作:PRec(@t.Rec)。成员:=0@雷姆科:我知道。原始代码使用
    ,我希望将代码更改保持在最低限度。不幸的是,这会污染主类。那我们就可以完全放弃这项记录了。创建这些属性意味着大量的工作。使用记录仍然有好处。它们在TPersistent.AssignTo中很有用。此外,如果你有一个内存密集型应用程序,你可以使用打包的记录来减少内存使用(性能成本),你可能是对的。正如我所说,我在这里谈论的是现有的代码。在D2007中,当某些东西工作(我指的是编译)时,我不希望它在下一个版本中被破坏。好吧,你依赖于一个要求修复很长时间的bug。看来它终于修好了。;-)@只是因为有东西被编译了
    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.