Question

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!

Was it helpful?

Solution

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.

OTHER TIPS

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

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;

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.

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;

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;
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top