2011-12-15 7 views
8

Po zapytaniu this question about interface fields in records Sądziłem, że po będzie działać (zauważ twierdzenie):Funkcja rekord powrocie z pola interfejsu

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; 

testowałem to z następującego kodu:

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

oraz dochodzenia nie działa!

Gdzie jest mój błąd? Jakie założenie jest błędne?

+0

Czy asercja kończy się niepowodzeniem podczas pierwszego lub drugiego uruchomienia pętli? – Johan

+0

@Smasher FWIW, to jest to o czym myślałem, kiedy napisałem moją błędną odpowiedź: http://stackoverflow.com/questions/5102843/delphi-function-result-nottedempted-during-for-loop –

Odpowiedz

12

Funkcja

function ReturnRec: TRec<Integer>; 

jest semantycznie równa procedury

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

[Jestem pewien, że ktoś z Embarcadero, prawdopodobnie Barry Kelly czy Alan Bauer stwierdził to gdzieś, ale nie mogę znaleźć referencja w tej chwili.]

W drugim przypadku kompilator zakłada, że ​​rekord zostanie zainicjowany (w razie potrzeby), zanim zostanie przekazany do ReturnRec i nie tworzy żadnego kodu inicjalizacyjnego dla rec wewnątrz ReturnRec. Zakładam, że ta sama ścieżka kodowa wewnątrz kompilatora jest brana dla pierwszego przykładu i dlatego Wynik nie jest inicjowany.

Zresztą, rozwiązanie jest proste:

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

Wystarczy założyć, że kompilator nie wie, co robi i przypisać interfejs i wszystko będzie działać dobrze.

EDIT

Problem masz wystąpi z pętli 'for'. Kod

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

jest kompilowany do czegoś takiego:

var 
    result: TRec<Integer>; 

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

Dlatego jesteś ponowne sam rekord całego i dlatego Result.Intf jest niezainicjowany tylko za pierwszym razem.

EDIT2

Można oszukać kompilator przesuwając t.ReturnRec zawołać z pętli do oddzielnego metody.

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

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

Teraz ukryta zmienna wyników żyje w procedurze GetRec i jest inicjowana za każdym razem, gdy wywoływana jest metoda GetRec.

+0

Dzięki gabr! Znam to obejście, ale nie podoba mi się to, ponieważ używam tego dla rekordu listy narzędzi (jak TList , ale jako rekord) i wywołanie 'Initialize' we wszystkich miejscach nie jest bardzo miłe (rodzaj eliminuje część korzyści o płycie) :(zwłaszcza, że ​​zapomnienie może prowadzić do kilku nieprzyjemnych błędów – jpfollenius

+2

@Smasher Powinieneś zawsze przypisać wartości zwracane. Nie zwracaj niezainicjowanych wartości. –

+0

@David: okay, ale do tej pory zakładałem, że zwracana wartość ** jest ** zainicjowana, ponieważ jedyne odpowiednie pole jest typu interfejsu. Byłoby to bardzo miłe dla mojego scenariusza użycia. – jpfollenius

Powiązane problemy