在Delphi的匿名方法中引用的变量和何时被捕获? [英] How and when are variables referenced in Delphi's anonymous methods captured?

查看:237
本文介绍了在Delphi的匿名方法中引用的变量和何时被捕获?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

这是如何比较TFunc / TProc包含对象的功能/过程?,具体由David对Barry的问题的评论。因为我没有博客发布这个,我将在这里提出这个问题,并回答。

This was prompted by How to compare TFunc/TProc containing function/procedure of object?, specifically by David's comment to Barry's question. Since I don't have a Blog to post this to I'm going to ask this question here, and answer it.

问题:何时以及如何捕获Delphi的匿名方法中引用的变量?

Question: When and how are variables referenced in Delphi's anonymous methods captured?

示例:

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;

ShowMessage 将显示 2 。为什么?

推荐答案

当你有一个函数像问题中的那个,你有一个访问局部变量的匿名方法,Delphi似乎创建一个TInterfacedObject后代,它捕获所有基于堆栈的变量,因为它是自己的公共变量。使用Barry的技巧来实现TObject和一些RTTI,我们可以看到这一切都在行动。

When you have a function like the one in the question, where you have an anonymous method accessing a local variable, Delphi appears to create one TInterfacedObject descendant that captures all the stack based variables as it's own public variables. Using Barry's trick to get to the implementing TObject and a bit of RTTI we can see this whole thing in action.

实现后面的魔术代码可能如下所示: / p>

The magic code behind the implementation probably looks like this:

// 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;

当然这个代码不能编译。我是无魔法的:-)但是这里的想法是在幕后创建了一个魔术对象,并且从匿名方法引用的局部变量被转换为魔术对象的公共字段。该对象用作接口(IUnkown),因此它被引用计数。显然,相同的对象捕获所有使用的变量,并定义所有的匿名方法。

Of course this code doesn't compile. I'm magic-less :-) But the idea here is that an "Magic" object is created behind the scenes and local variables that are referenced from the anonymous method are transformed in public fields of the magic object. That object is uses as an interface (IUnkown) so it gets reference-counted. Apparently the same object captures all used variables AND defines all the anonymous methods.

这应该回答When和How。

This should answer both "When" and "How".

这是我以前调查的代码。把一个TButton放在空白的表单上,这应该是整个单位。当您按下按钮时,屏幕上将显示以下内容:

Here's the code I used to investigate. Put a TButton on a blank form, this should be the whole unit. When you press the button you'll see the following on screen, in sequence:


  • 000000(虚假号码)

  • 000000(相同的数字):这两个匿名方法的证明实际上是作为同一个对象的方法实现的!

  • TForm25.Button1Click $ ActRec: TInterfacedObject :这显示了实现后面的对象,它来自于TInterfacedObject

  • OnStack:string :RTTI发现该对象上的此字段。

  • 自身:TForm25 :RTTI发现该对象上的此字段。用于获取 ClasVar

  • FRefCount:Integer 的值来自TInterfacedObject

  • Class Var - ShowMessage的结果。

  • 在堆栈 - ShowMessage的结果。

  • 000000 (bogus number)
  • 000000 (the same number): This proofs both anonymous methods are actually implemented as methods of the same object!
  • TForm25.Button1Click$ActRec: TInterfacedObject : This shows the object behind the implementation, it's derived from TInterfacedObject
  • OnStack:string: RTTI discovers this field on that object.
  • Self: TForm25: RTTI discovers this field on that object. It's used to get the value of ClasVar
  • FRefCount:Integer - this comes from TInterfacedObject
  • Class Var - result of ShowMessage.
  • On Stack - result of ShowMessage.

以下是代码:

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.

这篇关于在Delphi的匿名方法中引用的变量和何时被捕获?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆