Delphi: записи в классах - PullRequest
       23

Delphi: записи в классах

3 голосов
/ 17 декабря 2009

Следующая ситуация:

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 ]

6 голосов
/ 17 декабря 2009

Это

MyClass.Rec.Member := 0;

не компилируется специально. Тот факт, что оба «с» -конструкта когда-либо компилировались, был (AFAICT) простым упущением. Таким образом, оба не"безопасны в использовании".

Два безопасных решения:

  1. Назначьте MyClass.Rec для временной записи, которой вы управляете, и верните обратно MyClass.Rec.
  2. Выставить TMyClass.Rec.Member как собственное имущество.
3 голосов
/ 17 декабря 2009

В некоторых ситуациях, подобных этой, когда запись класса нуждается в «прямом манипулировании», я часто прибегаю к следующему:

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 - я стараюсь его не использовать! Brian

1 голос
/ 17 декабря 2009

Причина, по которой он не может быть назначен напрямую: здесь .
Что касается WITH, он все еще работает в D2009, и я ожидал бы, что он будет работать и в D2010 (который я не могу сейчас проверить).
Более безопасный подход - выставить свойство записи напрямую, как предложено Алленом в приведенной выше SO post :

property RecField: Integer read FRec.A write FRec.A;
0 голосов
/ 17 декабря 2009

Причина, по которой он был изменен, заключается в том, что это была ошибка компилятора. Тот факт, что он скомпилирован, не гарантирует, что он будет работать. Сбой, как только Геттер был добавлен к свойству

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 в том, что он позволил ему это сделать.

Если вы не можете напрямую назначить свойство, не используйте хак для его присвоения - оно когда-нибудь откажется.

Воспользуйтесь предложением Брайана, чтобы вернуть указатель, но отбросьте С - вы можете сделать Point.X: = 10;

.
0 голосов
/ 17 декабря 2009

Другое решение - использовать вспомогательную функцию:

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

Это все еще небезопасно (см. Комментарий Барри Келли о добытчике / сеттере)

/ Edit: ниже следует самый уродливый хак (и, вероятно, самый небезопасный), но это было так забавно, что я должен был опубликовать это

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;
0 голосов
/ 17 декабря 2009

Записи являются значениями , они не должны быть сущностями.

У них даже есть семантика присваивания при копировании! Вот почему вы не можете изменить значение свойства на месте. Потому что это нарушило бы семантику типа значения FRec и нарушило бы код, который полагался на его неизменность или, по крайней мере, на безопасную копию.

Они задаются вопросом: зачем вам значение (ваш TRec), чтобы вести себя как объект / сущность?

Не лучше ли было бы, чтобы TRec был классом, если это то, для чего вы его используете?

Суть в том, что когда вы начнете использовать языковую функцию за ее пределами, вы легко сможете оказаться в ситуации, когда вам придется сражаться со своими инструментами каждый метр на пути.

...