2009-03-06 35 views
16

Tôi tò mò muốn biết lý do tại sao Delphi đối xử với thuộc tính loại kỷ lục như chỉ đọc:"bên trái không thể được gán cho" đối với tài sản loại kỷ lục trong 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; 

Nếu tôi cố gắng gán một giá trị cho bất kỳ các thành viên của bất động sản Rec, tôi sẽ nhận được "bên trái không thể được gán cho" lỗi:

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

trong khi làm việc cùng với các lĩnh vực cơ bản được phép:

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

Có giải thích nào cho hành vi đó không?

Trân

Trả lời

27

Kể từ khi "Rec" là một tài sản, trình biên dịch xử lý nó một chút khác nhau bởi vì nó có đến đầu tiên đánh giá "đọc" của decl tài sản. Xem xét việc này, đó là ngữ nghĩa tương đương với ví dụ của bạn:

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

Nếu bạn nhìn vào nó như thế này, bạn có thể thấy rằng các tài liệu tham khảo đầu tiên "Rec" (trước dấu chấm ''), có gọi GetRec , sẽ tạo bản sao tạm thời của Rec. Những thời gian này là do thiết kế "chỉ đọc". Đây là những gì bạn đang chạy vào.

Một điều bạn có thể làm ở đây là để thoát ra khỏi các lĩnh vực cá nhân của kỷ lục như tài sản trên lớp chứa:

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

này sẽ cho phép bạn gán trực tiếp thông qua tài sản cho lĩnh vực mà nhúng ghi trong cá thể lớp.

+1

1 Bumped vào đây 4 năm sau khi câu trả lời của bạn! –

4

Bởi vì bạn có hàm getter và setter ngầm và bạn không thể sửa đổi kết quả của hàm vì nó là tham số const.

(Lưu ý: Trong trường hợp bạn chuyển đổi bản ghi trong một đối tượng, kết quả sẽ thực sự là một con trỏ, do đó tương đương với tham số var).

Nếu bạn muốn ở lại với Bản ghi, bạn phải sử dụng biến số trung gian (hoặc biến Trường) hoặc sử dụng câu lệnh WITH.

Xem các hành vi khác nhau trong các mã sau đây với các chức năng getter và setter rõ ràng:

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

Có điều này là một vấn đề. Nhưng vấn đề có thể được giải quyết bằng cách sử dụng các thuộc tính bản ghi:

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; 

Điều này biên dịch và hoạt động không có vấn đề gì.

+3

+1 Lưu ý rằng giải pháp của bạn không tồi, nhưng người dùng cần nhớ rằng nếu họ thay đổi thuộc tính thành "thuộc tính Rec: TRec đọc GetRec write FRec;", mẹo gán sẽ không thành công (vì GetRec sẽ trả về * sao chép * dưới dạng bản ghi * các loại giá trị *). –

+0

Thuộc tính Rec trong TForm1 chỉ có thể đọc nếu chỉ đọc/ghi truy cập vào các thuộc tính của bản ghi được yêu cầu. Phần quan trọng của giải pháp này là các phương thức setter trong các thuộc tính của bản ghi. – Griffyn

8

Trình biên dịch dừng bạn chỉ định tạm thời. Tương đương trong C# được cho phép, nhưng nó không có hiệu lực; giá trị trả về của thuộc tính Rec là một bản sao của trường cơ sở, và gán cho trường trên bản sao là một nop.

2

Giống như những người khác đã nói - thuộc tính đã đọc sẽ trả lại bản sao của bản ghi, do đó việc gán trường không hoạt động trên bản sao thuộc sở hữu của TForm1.

lựa chọn khác là cái gì đó như:

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 sẽ dereference con trỏ Prec cho bạn, để mọi thứ như thế này vẫn sẽ làm việc:

Form1.Rec.A := 1234; 

Không cần cho một phần ghi của tài sản, trừ khi bạn muốn trao đổi bộ đệm PREC mà FREC trỏ vào. Tôi thực sự sẽ không đề nghị làm như vậy trao đổi thông qua một tài sản anyway.

2

Phương pháp đơn giản nhất là:

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

Tôi nghĩ rằng bạn đang đúng - không có điểm sử dụng tài sản cho hồ sơ, nó có vẻ như rất nhiều công việc ... chỉ cần có một thủ tục làm một cái gì đó để ghi lại: SetSomething (var AREC: TRec) – sergeantKK

3

Điều này là do bất động sản đang thực sự tuân thủ như một hàm. Thuộc tính chỉ trả lại hoặc đặt giá trị. Nó không phải là một tham chiếu hoặc một con trỏ đến bản ghi

vậy:

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

là giống như gọi một hàm như thế này:

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

gì bạn có thể làm là:

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

Đó là một chút lộn xộn nhưng vốn có trong loại kiến ​​trúc này.

7

Giải pháp tôi thường sử dụng là khai báo thuộc tính dưới dạng con trỏ tới bản ghi.

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; 

Với điều này, trực tiếp giao Form1.Rec.A := MyInteger sẽ làm việc, nhưng cũng Form1.Rec := MyRec sẽ làm việc bằng cách sao chép tất cả các giá trị trong MyRec đến lĩnh vực FRec như mong đợi.

Các pitfall duy nhất ở đây là khi bạn muốn thực sự lấy một bản sao của hồ sơ để làm việc với, bạn sẽ có một cái gì đó giống như MyRec := Form1.Rec^

+0

Rất tuyệt vời và chuyên nghiệp – Vassilis

Các vấn đề liên quan