2011-12-15 33 views
8

Sau khi hỏi this question about interface fields in records Tôi cho rằng sau đây sẽ làm việc (chú ý khẳng định):Chức năng quay trở lại kỷ lục với lĩnh vực giao diện

type 
    TRec <T> = record 
    Intf : IInterface; 
    end; 

    TTestClass = class 
    public 
    function ReturnRec : TRec <Integer>; 
    end; 

    // Implementation 
    function TTestClass.ReturnRec : TRec <Integer>; 
    begin 
    Assert (Result.Intf = nil); // Interface field in record should be initialized! 
    Result.Intf := TInterfacedObject.Create; 
    end; 

Tôi thử nghiệm này với đoạn mã sau:

for I := 1 to 1000 do 
    Rec := Test.ReturnRec; 

và sự khẳng định thất bại!

Lỗi lầm của tôi ở đâu? Giả định nào là sai?

+0

Xác nhận không thực hiện được trong lần chạy đầu tiên hoặc thứ hai của vòng lặp? – Johan

+0

@Smasher FWIW, đây là những gì tôi đã nghĩ đến khi tôi viết câu trả lời sai: http://stackoverflow.com/questions/5102843/delphi-function-result-not-emptied-during-for-loop –

Trả lời

12

Chức năng

function ReturnRec: TRec<Integer>; 

là ngữ nghĩa tương đương với thủ tục

procedure ReturnRec(var Result: TRec<Integer>); 

[Tôi khá chắc chắn rằng ai đó từ Embarcadero, có lẽ Barry Kelly hay Alan Bauer nói này ở đâu đó nhưng tôi không thể tìm thấy tham chiếu tại thời điểm này.]

Trong trường hợp thứ hai, trình biên dịch giả định rằng bản ghi sẽ được khởi tạo (nếu cần) trước khi nó được chuyển đến ReturnRec và không tạo bất kỳ mã khởi tạo nào cho rec bên trong ReturnRec. Tôi giả sử rằng cùng một đường dẫn mã bên trong trình biên dịch được lấy cho ví dụ đầu tiên và đó là lý do tại sao kết quả không được khởi tạo.

Dù sao, giải pháp rất đơn giản:

function TTestClass.ReturnRec : TRec <Integer>; 
begin 
    Result.Intf := TInterfacedObject.Create; 
end; 

Chỉ cần giả định trình biên dịch mà biết những gì nó làm và phân công giao diện và tất cả mọi thứ sẽ chỉ làm việc tốt.

EDIT

Sự cố bạn đã xảy ra từ vòng lặp 'for'. Mã của bạn

for I := 1 to 1000 do 
    Rec := Test.ReturnRec; 

được biên dịch vào một cái gì đó như thế này:

var 
    result: TRec<Integer>; 

Initialize(result); 
for I := 1 to 1000 do begin 
    Test.ReturnRec(result); 
    rec := result; 
end; 

Đó là lý do tại sao bạn đang sử dụng lại cùng một kỷ lục khắp nơi và đó là lý do tại sao Result.Intf chưa được định hình chỉ lần đầu tiên.

EDIT2

Bạn có thể đánh lừa trình biên dịch bằng cách di chuyển t.ReturnRec gọi ra khỏi vòng lặp thành một phương pháp riêng biệt.

procedure GetRec(t: TTest; var rec: TRec); 
begin 
    rec := t.ReturnRec; 
end; 

for i := 1 to 1000 do 
    GetRec(t, rec); 

Bây giờ biến kết quả ẩn sống trong thủ tục GetRec và được khởi tạo mỗi khi GetRec được gọi.

+0

Cảm ơn gabr! Tôi biết cách giải quyết nhưng tôi không thích nó, bởi vì tôi đang sử dụng nó cho một bản ghi danh sách tiện ích (như TList nhưng như bản ghi) và gọi 'Initialize' ở mọi nơi không đẹp lắm (loại bỏ một phần lợi thế của có một bản ghi) :(đặc biệt là kể từ khi quên nó có thể dẫn đến một số lỗi khó chịu – jpfollenius

+2

@Smasher Bạn nên luôn luôn gán các giá trị trả về.Không trả về các giá trị unininitialzed. –

+0

@David: okay, nhưng cho đến bây giờ tôi giả định rằng giá trị trả về ** được ** khởi tạo, vì trường chỉ có liên quan là một loại giao diện. Điều đó sẽ rất tốt cho kịch bản sử dụng của tôi. – jpfollenius

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