Может ли запись использоваться как свойство объекта?

Я хотел бы сделать запись как свойство объекта. Проблема в том, что когда я меняю одно из полей этой записи, объект не знает об этом изменении.

type
  TMyRecord = record
    SomeField: Integer;
  end;

  TMyObject = class(TObject)
  private
    FSomeRecord: TMyRecord;
    procedure SetSomeRecord(const Value: TMyRecord);
  public
    property SomeRecord: TMyRecord read FSomeRecord write SetSomeRecord;
  end;

И тогда, если я это сделаю...

MyObject.SomeRecord.SomeField:= 5;

... не будет работать.

Итак, как мне сделать процедуру установки свойств "catch", когда записывается одно из полей записи? Возможно, какой-то трюк в том, как объявить запись?

Дополнительная информация

Моя цель - не создавать TObject или TPersistent с событием OnChange (например, TFont или TStringList). Я более чем знаком с использованием объектов для этого, но, пытаясь немного очистить свой код, я вижу, могу ли я использовать запись вместо этого. Мне просто нужно убедиться, что мой установщик свойств записи можно вызвать правильно, когда я установил одно из полей записи.

Ответ 1

Рассмотрим эту строку:

MyObject.SomeRecord.SomeField := NewValue;

Это на самом деле ошибка компиляции:

[Ошибка DCC]: E2064 Левая сторона не может быть назначена

Ваш фактический код, вероятно, примерно такой:

MyRecord := MyObject.SomeRecord;
MyRecord.SomeField := NewValue;

Здесь происходит копирование значения типа записи в локальную переменную MyRecord. Затем вы изменяете поле этой локальной копии. Это не изменяет запись, содержащуюся в MyObject. Для этого вам нужно вызвать средство настройки свойств.

MyRecord := MyObject.SomeRecord;
MyRecord.SomeField := NewValue;
MyObject.SomeRecord := MyRecord;

Или переключитесь на использование ссылочного типа, то есть на класс, а не на запись.

Подводя итог, проблема с вашим текущим кодом заключается в том, что SetSomeRecord не вызывается, и вместо этого вы изменяете копию записи. И это потому, что запись является значением типа, а не типом .

Ответ 2

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

type
  TMyRec = record
    SomeRecInteger: integer;
    SomeRecString: string;
  end;

  TMyClass = class(TObject)
  private
    FMyRec: TMyRec;
    procedure SetSomeString(const AString: string);
  public
    property SomeInteger: integer read FMyRec.SomeRecInteger write FMyRec.SomeRecInteger;
    property SomeString: string read FMyRec.SomeRecString write SetSomeString;
  end;

procedure TMyClass.SetSomeRecString(const AString: string);
begin
  If AString <> SomeString then
  begin
    // do something special if SomeRecString property is set
    FMyRec.SomeRecString := AString;
  end;
end;

Примечание:

  • Прямой доступ к свойству записи SomeRecInteger
  • Использование SetSomeRecString для реализации специальной обработки только в этом поле.

Надеюсь, что это поможет.

Ответ 3

Как использовать TObject вместо записи?

type
  TMyProperties = class(TObject)
    SomeField: Integer;
  end;

  TMyObject = class(TObject)
  private
    FMyProperties: TMyProperties;     
  public
    constructor Create;
    destructor Destroy; override;

    property MyProperties: TMyRecord read FMyProperties;
  end;

implementation

constructor TMyObject.Create;
begin
  FMyProperties := TMyProperties.Create;
end;

destructor TMyObject.Destroy;
begin
  FMyProperties.Free;
end;

Теперь вы можете читать и устанавливать свойства TMyProperties следующим образом:

MyObject.MyProperties.SomeField := 1;
x := MyObject.MyProperties.SomeField;

Используя этот метод, вам не нужно индивидуально получать/устанавливать значения в/из записи. Если вам нужно поймать изменения в FMyProperties, вы можете добавить процедуру "set" в объявлении свойства.

Ответ 4

Почему бы не сделать часть setter/getter записи?

TMyRecord = record
  fFirstname: string;
  procedure SetFirstName(AValue: String);
property
  Firstname : string read fFirstname write SetFirstName;
end;

TMyClass = class(TObject)
  MyRecord : TMyRecord;
end;

procedure TMyRecord.SetFirstName(AValue: String);
begin
  // do extra checking here
  fFirstname := AValue;
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  MyClass: TMyClass;
begin
  MyClass := TMyClass.Create;
  try
    MyClass.MyRecord.Firstname := 'John';
    showmessage(MyClass.MyRecord.Firstname);
  finally
    MyClass.Free;
  end;
end;

Ответ 5

Вы передаете запись по значению, поэтому копия записи хранится объектом. С этого момента есть два объекта; оригинал и копия объекта. Изменение одного не изменит другого.

Вам нужно передать запись по ссылке.

type
  TMyRecord = record
    SomeField: Integer;
  end;
  PMyRecord = ^TMyRecord;

  TMyObject = class(TObject)
  private
    FSomeRecord: PMyRecord;
    procedure SetSomeRecord(const Value: PMyRecord);
  public
    property SomeRecord: PMyRecord read FSomeRecord write SetSomeRecord;
  end;

Ответ 6

это альтернатива ответа @SourceMaid.

Вы можете использовать запись (не указатель на запись) внутри вашего объекта и иметь свойство только для чтения, которое возвращает указатель на вашу запись.

класс:

type
  TMyRecord = record
    I:Integer;
  end;
  PMyRecord = ^TMyRecord;
  TMyObject = class
  private
    FMyRecord:TMyRecord;
  function GetMyRecordPointer: PMyRecord;
  public
    property MyRecord: PMyRecord read GetMyRecordPointer;
  end;

геттер:

function TMyObject.GetMyRecordPointer: PMyRecord;
begin
  result := @FMyRecord;
end;

использование:

o := TMyObject.Create;
o.MyRecord.I := 42;
ShowMessage(o.MyRecord.I.ToString);
o.MyRecord.I := 23;
ShowMessage(o.MyRecord.I.ToString);
o.Free;

вам не нужен сеттер, потому что вы получаете ссылку и работаете. это означает, что вы не можете изменить всю запись, назначив новую.
но вы можете напрямую манипулировать элементами записи без получения ошибки "Left side cannot be assigned to".