2012-02-06 3 views
11

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

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; 

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

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

Подробнее

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

+0

Возможно ли, что «упакованная запись» подходит? –

+1

'упакован' или иначе находится рядом с точкой. Это просто контролирует компоновку и выравнивание записи. Основная проблема заключается в различии между типами значений и ссылочными типами. На самом деле код в вашем вопросе даже не компилируется. –

+0

@DavidHeffernan Очень верно, я на самом деле не поместил этот код в Delphi, набрал его прямо здесь. –

ответ

11

Рассмотрим следующую строку:

MyObject.SomeRecord.SomeField := NewValue; 

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

[DCC Error]: E2064 Left side cannot be assigned to

Ваш фактический код, вероятно, что-то вроде этого:

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

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

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

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

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

+0

+1 Итак, это означает, что нет абсолютно никакого способа заставить установщика свойств записи уловить модификацию своих полей? Bummer :( –

+0

Это верно. –

+1

Это также относится к объектно-ориентированным свойствам, поэтому классы типа 'TFont',' TStrings' и т. Д. Имеют событие OnChange, родительский компонент назначает внутренний обработчик событий для этого событие, поэтому изменения в под-свойствах могут быть переданы до родительского компонента, так как его свойство не будет вызываться в этой ситуации. –

1

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

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

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

Это делает класс очень неудобным, потому что вы вынуждаете клиентов класса выделять запись и следите за тем, чтобы ее жизненный цикл отслеживал объект. Разумеется, вы хотели сохранить запись в классе и открыть свойство указателя только для чтения, которое реализовано с помощью функции getter. Реализован как результат: = @FSomeRecord, где FSomeRecord - TMyRecord. –

+0

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

+0

@DavidHeffernan Согласен, что только вводит больше трудностей. –

4

Как насчет использования 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, вы можете добавить процедуру «установить» в объявлении свойства.

+0

Действительно, я довольно часто использую для этого объекты (и постоянные). Он также состоит обычно из чего-то, чего у вас нет: событие OnChange. По сути, все мое дело в том, чтобы уловить изменения этой записи (или объекта) в процедуре 'SetSomeRecord'.Я искал, можно ли инкапсулировать то же самое в записи, не требуя события OnChange. –

10

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

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; 

Примечание:

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

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

+0

+1 Для удобства я бы также добавил процедуру, которая принимает параметр TMyRec, чтобы я мог задайте все значения за один раз. –

+1

+1 Не совсем то, что я искал, но научил меня чему-то новому: I n когда-либо знал, что вы можете использовать поля записи в качестве свойств getters/seters. –

4

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

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; 
+0

Woah, подождите, вы можете это сделать ?! Это искажает все знания, которые у меня когда-либо были из записей! –

+0

Да, он был добавлен к новым компиляторам, не знаю, с какой версии он начинался с (я использую 2010). –

+1

Свойства здесь не нужны. То, что вы на самом деле делали здесь, заключалось в том, что запись стала публичным участником, а не собственностью. Это большие перемены. –

0

это альтернатива ответам @ 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".

Смежные вопросы