Может ли запись использоваться как свойство объекта?
Я хотел бы сделать запись как свойство объекта. Проблема в том, что когда я меняю одно из полей этой записи, объект не знает об этом изменении.
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"
.