2011-03-01 10 views
18

Esto fue provocado por How to compare TFunc/TProc containing function/procedure of object?, específicamente por el comentario de David a la pregunta de Barry. Como no tengo un Blog para publicar esto, voy a hacer esta pregunta aquí y responderla.¿Cómo y cuándo se toman las variables a las que se hace referencia en los métodos anónimos de Delphi?

Pregunta: ¿Cuándo y cómo se capturan las variables a las que se hace referencia en los métodos anónimos de Delphi?

Ejemplo:

procedure ProcedureThatUsesAnonymousMethods; 
var V: string; 
    F1: TFunc<string>; 
    F2: TFunc<string>; 
begin 
    F1 := function: string 
     begin 
      Result := V; // references local variable 
     end 
    V := '1'; 
    F2 := function: string 
     begin 
      Result := V; 
     end 
V := '2'; 
ShowMessage(F1); 
ShowMessage(F2); 
end; 

Tanto ShowMessage se van a mostrar 2. ¿Por qué? ¿Cómo se captura el V y cuándo?

+2

desplazamiento a lo que también es más útil para la comunidad. (Y, por supuesto, ¡obtienes mucho más reputación publicando aquí! :)) –

+1

Hm ... ¿Las funciones/procedimientos se llaman "métodos" si no pertenecen a un objeto? –

+0

que versión delphi estás usando? – kiw

Respuesta

21

Cuando tiene una función como la de la pregunta, donde tiene un método anónimo para acceder a una variable local, Delphi parece crear un descendiente TInterfacedObject que captura todas las variables basadas en la pila como sus propias variables públicas. Usando el truco de Barry para llegar al TObject de implementación y un poco de RTTI, podemos ver todo esto en acción.

El código magia detrás de la puesta en práctica probablemente se parece a esto:

// Magic object that holds what would normally be Stack variables and implements 
// anonymous methods. 
type ProcedureThatUsesAnonymousMethods$ActRec = class(TInterfacedObject) 
public 
    V: string; 
    function AnonMethodImp: string; 
end; 

// The procedure with all the magic brought to light 
procedure ProcedureThatUsesAnonymousMethods; 
var MagicInterface: IUnknown; 
    F1: TFunc<string>; 
    F2: TFunc<string>; 
begin 
    MagicInterface := ProcedureThatUsesAnonymousMethods$ActRec.Create; 
    try 
    F1 := MagicInterface.AnonMethod; 
    MagicInterface.V := '1'; 
    F2 := MagicInterface.SomeOtherAnonMethod; 
    MagicInterface.V := '2'; 
    ShowMessage(F1); 
    ShowMessage(F2); 
    finally MagicInterface := nil; 
    end; 
end; 

Por supuesto, este código no compila. No tengo magia :-) Pero la idea aquí es que se crea un objeto "mágico" detrás de escena y las variables locales que se referencian desde el método anónimo se transforman en campos públicos del objeto mágico. Ese objeto se usa como una interfaz (IUnkown) para que se cuente como referencia. Aparentemente, el mismo objeto captura todas las variables usadas Y define todos los métodos anónimos.

Esto debería responder tanto a "Cuando" como a "Cómo".

Aquí está el código que solía investigar. Ponga un TButton en una forma en blanco, esta debería ser la unidad completa. Cuando se pulsa el botón verá la siguiente información en pantalla, en secuencia:

  • 000000 (número de falsos)
  • 000000 (el mismo número): Esta pruebas tanto los métodos anónimos sean implementadas como métodos del mismo objeto!
  • TForm25.Button1Click$ActRec: TInterfacedObject: Esto muestra el objeto detrás de la aplicación, se deriva de TInterfacedObject
  • OnStack:string: RTTI descubre este campo en ese objeto.
  • Self: TForm25: RTTI descubre este campo en ese objeto. Se utiliza para obtener el valor de ClasVar
  • FRefCount:Integer - esto viene de TInterfacedObject
  • Class Var - resultado de ShowMessage.
  • On Stack - resultado de ShowMessage.

Aquí está el código:

unit Unit25; 

interface 

uses 
    Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, 
    Dialogs, StdCtrls, Rtti; 

type 
    TForm25 = class(TForm) 
    Button1: TButton; 
    procedure Button1Click(Sender: TObject); 
    private 
    ClassVar: string; 
    public 
    end; 

var 
    Form25: TForm25; 

implementation 

{$R *.dfm} 

procedure TForm25.Button1Click(Sender: TObject); 
var F1: TFunc<string>; 
    F2: TFunc<string>; 

    OnStack: string; 

    i: IInterface; 
    o: TObject; 

    RC: TRttiContext; 
    R: TRttiType; 
    RF: TRttiField; 

begin 
    // This anonymous method references a member field of the TForm class 
    F1 := function :string 
     begin 
      Result := ClassVar; 
     end; 

    i := PUnknown(@F1)^; 
    o := i as TObject; 
    ShowMessage(IntToStr(Integer(o))); // I'm looking at the pointer to see if it's the same instance as the one for the other Anonymous method 

    // This anonymous method references a stack variable 
    F2 := function :string 
     begin 
      Result := OnStack; 
     end; 

    i := PUnknown(@F2)^; 
    o := i as TObject; 
    ShowMessage(IntToStr(Integer(o))); 

    ShowMessage(o.ClassName + ': ' + o.ClassType.ClassParent.ClassName); 

    RC.Create; 
    try 
    R := RC.GetType(o.ClassType); 
    for RF in R.GetFields do 
     ShowMessage(RF.Name + ':' + RF.FieldType.Name); 
    finally RC.Free; 
    end; 

    ClassVar := 'Class Var'; 
    OnStack := 'On Stack'; 

    ShowMessage(F1); 
    ShowMessage(F2); 
end; 

end. 
Cuestiones relacionadas