2009-03-06 3 views
16

Я хотел бы знать, почему Delphi обрабатывает свойства типа записи только для чтения:«Левая сторона не может быть назначен» для свойств типов записей в Delphi

TRec = record 
    A : integer; 
    B : string; 
    end; 

    TForm1 = class(TForm) 
    private 
    FRec : TRec; 
    public 
    procedure DoSomething(ARec: TRec); 
    property Rec : TRec read FRec write FRec; 
    end; 

При попытке присвоить значение любого из члены Rec собственности, я буду получать «Левая сторона не может быть назначен на» ошибки:

procedure TForm1.DoSomething(ARec: TRec); 
begin 
    Rec.A := ARec.A; 
end; 

, делая то же самое с основным полем допускается:

procedure TForm1.DoSomething(ARec: TRec); 
begin 
    FRec.A := ARec.A; 
end; 

Есть ли объяснения этого поведения?

С уважением

ответ

27

С «Rec» это свойство, компилятор обрабатывает его немного по-другому, потому что он должен сначала оценить «читать» из ДЕЦЛ собственности. Рассмотрим это, что семантически эквивалентно вашему примеру:

... 
property Rec: TRec read GetRec write FRec; 
... 

Если смотреть на это так, вы можете увидеть, что первая ссылка на «Rec» (перед точкой «»), должен вызвать GetRec , который создаст временную локальную копию Rec. Эти временные рамки по дизайну «только для чтения». Это то, с чем вы сталкиваетесь.

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

... 
property RecField: Integer read FRec.A write FRec.A; 
... 

Это позволит вам напрямую назначить через свойство в области, что внедренного запись в экземпляре класса.

+1

+1 наталкивались на эти 4 года после вашего ответа! –

4

Поскольку у вас есть неявные функции getter и setter, и вы не можете изменить результат функции, поскольку это параметр const.

(Примечание: если вы преобразуете запись в объект, результатом будет фактически указатель, что эквивалентно параметру var).

Если вы хотите остаться с записью, вы должны использовать промежуточную переменную (или переменную поля) или использовать инструкцию WITH.

Смотрите различные модели поведения в следующем коде с явным получения и установки функций:

type 
    TRec = record 
    A: Integer; 
    B: string; 
    end; 

    TForm2 = class(TForm) 
    private 
    FRec : TRec; 
    FRec2: TRec; 
    procedure SetRec2(const Value: TRec); 
    function GetRec2: TRec; 
    public 
    procedure DoSomething(ARec: TRec); 
    property Rec: TRec read FRec write FRec; 
    property Rec2: TRec read GetRec2 write SetRec2; 
    end; 

var 
    Form2: TForm2; 

implementation 

{$R *.dfm} 

{ TForm2 } 

procedure TForm2.DoSomething(ARec: TRec); 
var 
    LocalRec: TRec; 
begin 
    // copy in a local variable 
    LocalRec := Rec2; 
    LocalRec.A := Arec.A; // works 

    // try to modify the Result of a function (a const) => NOT ALLOWED 
    Rec2.A := Arec.A; // compiler refused! 

    with Rec do 
    A := ARec.A; // works with original property and with! 
end; 

function TForm2.GetRec2: TRec; 
begin 
    Result:=FRec2; 
end; 

procedure TForm2.SetRec2(const Value: TRec); 
begin 
    FRec2 := Value; 
end; 
19

Да, это проблема. Но проблема может быть решена с использованием свойств записи:

type 
    TRec = record 
    private 
    FA : integer; 
    FB : string; 
    procedure SetA(const Value: Integer); 
    procedure SetB(const Value: string); 
    public 
    property A: Integer read FA write SetA; 
    property B: string read FB write SetB; 
    end; 

procedure TRec.SetA(const Value: Integer); 
begin 
    FA := Value; 
end; 

procedure TRec.SetB(const Value: string); 
begin 
    FB := Value; 
end; 

TForm1 = class(TForm) 
    Button1: TButton; 
    procedure Button1Click(Sender: TObject); 
private 
    { Private declarations } 
    FRec : TRec; 
public 
    { Public declarations } 
    property Rec : TRec read FRec write FRec; 
end; 

procedure TForm1.Button1Click(Sender: TObject); 
begin 
    Rec.A := 21; 
    Rec.B := 'Hi'; 
end; 

Это компилируется и работает без проблем.

+3

+1 Обратите внимание, что ваше решение неплохое, но пользователи его должны помнить, что если они когда-либо изменят свойство на «свойство Rec: TRec, прочитайте GetRec write FRec;», трюк назначения потерпит неудачу (потому что GetRec вернет * copy *, поскольку записи - это типы значений *). –

+0

Свойство Rec в TForm1 может быть прочитано только в том случае, если требуется только чтение/запись доступа к свойствам записи. Ключевой частью этого решения являются методы настройки в свойствах записи. – Griffyn

8

Компилятор запрещает вам назначать временное. Эквивалент в C# разрешен, но он не действует; возвращаемое значение свойства Rec является копией базового поля, а присвоение поля на копии является nop.

2

Как и другие люди, свойство read будет возвращать копию записи, поэтому присвоение полей не действует на копию, принадлежащую TForm1.

Другой вариант что-то вроде:

TRec = record 
    A : integer; 
    B : string; 
    end; 
    PRec = ^TRec; 

    TForm1 = class(TForm) 
    private 
    FRec : PRec; 
    public 
    constructor Create; 
    destructor Destroy; override; 

    procedure DoSomething(ARec: TRec); 
    property Rec : PRec read FRec; 
    end; 

constructor TForm1.Create; 
begin 
    inherited; 
    FRec := AllocMem(sizeof(TRec)); 
end; 

destructor TForm1.Destroy; 
begin 
    FreeMem(FRec); 

    inherited; 
end; 

Delphi будет разыменования указателя PrEC для вас, так что такие вещи, как это будет работать:

Form1.Rec.A := 1234; 

Там нет необходимости для записи части из свойство, если вы не захотите поменять буфер PRec, на который указывает FRec. В любом случае я бы не стал предлагать такую ​​замену через собственность.

2

Самый простой подход:

procedure TForm1.DoSomething(ARec: TRec); 
begin 
    with Rec do 
    A := ARec.A; 
end; 
+0

Я думаю, что вы правы - нет смысла использовать свойства для записей, это похоже на большую работу ... просто выполните процедуру, которая что-то делает для записи: SetSomething (var ARec: TRec) – sergeantKK

3

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

так:

Testing.TestRecord.I := 10; // error 

является такой же, как вызов функции так:

Testing.getTestRecord().I := 10; //error (i think) 

, что вы можете сделать, это:

r := Testing.TestRecord; // read 
r.I := 10; 
Testing.TestRecord := r; //write 

Это немного грязный, но присущий этому типу архитектуры.

7

Решение, которое я часто использую, заключается в объявлении свойства как указателя на запись.

type 
    PRec = ^TRec; 
    TRec = record 
    A : integer; 
    B : string; 
    end; 

    TForm1 = class(TForm) 
    private 
    FRec : TRec; 

    function GetRec: PRec; 
    procedure SetRec(Value: PRec); 
    public 
    property Rec : PRec read GetRec write SetRec; 
    end; 

implementation 

function TForm1.GetRec: PRec; 
begin 
    Result := @FRec; 
end; 

procedure TForm1.SetRec(Value: PRec); 
begin 
    FRec := Value^; 
end; 

При этом, непосредственно назначая Form1.Rec.A := MyInteger будет работать, но и Form1.Rec := MyRec будет работать путем копирования всех значений в MyRec к FRec области, как и ожидалось.

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

+0

Превосходный и профессиональный – Vassilis