2011-12-15 8 views
8

Después de pedir this question about interface fields in records Supuse que el siguiente iba a funcionar (notar la afirmación):Función regresar registro con el campo de la interfaz

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; 

He probado esto con el siguiente código:

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

y la afirmación ¡falla!

¿Dónde está mi error aquí? ¿Qué suposición es incorrecta?

+0

¿La aserción falla en la primera o segunda ejecución del ciclo? – Johan

+0

@Smasher FWIW, esto es en lo que estaba pensando cuando escribí mi respuesta incorrecta: http://stackoverflow.com/questions/5102843/delphi-function-result-not-emptied-during-for-loop –

Respuesta

12

Función

function ReturnRec: TRec<Integer>; 

es semánticamente igual al procedimiento

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

[Estoy bastante seguro de que alguien de Embarcadero, probablemente Barry Kelly o Alan Bauer declaró esto en alguna parte, pero no puedo encontrar la referencia en este momento.]

En el segundo caso, el compilador supone que la grabación se inicializará (si es necesario) antes de pasarla a ReturnRec y no crea ningún código de inicialización para rec dentro de ReturnRec. Supongo que se toma la misma ruta de código dentro del compilador para el primer ejemplo y es por eso que el resultado no se inicializa.

De todos modos, la solución es simple:

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

a suponer que el compilador sabe lo que está haciendo y asignar la interfaz y todo funcionará bien.

EDITAR

El problema que tiene se produce a partir del bucle 'para'. Su código

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

se compila en algo como esto:

var 
    result: TRec<Integer>; 

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

Por eso va a reutilizar mismo registro por todas partes y es por eso que no está inicializado Result.Intf sólo la primera vez.

Edit2

Puede engañar al compilador moviendo t.ReturnRec llamar a cabo a partir del bucle en un método separado.

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

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

Ahora la variable de resultado oculta vive en el procedimiento GetRec y se inicializa cada vez que se llama a GetRec.

+0

¡Gracias gabr! Conozco la solución, pero no me gusta, porque la utilizo para un registro de lista de utilidades (como TList pero como registro) y llamar a 'Initialize' en todos los lugares no es muy bueno (algo que elimina parte de la ventaja de tener un registro) :(especialmente desde que olvidarlo puede conducir a algunos errores desagradables. – jpfollenius

+2

@Smasher Siempre debe asignar valores devueltos. No devuelva valores no inicializados. –

+0

@David: está bien, pero hasta ahora supuse que el valor de retorno ** está ** inicializado, ya que el único campo relevante es de tipo de interfaz. Eso hubiera sido muy bueno para mi escenario de uso. – jpfollenius

Cuestiones relacionadas