[英]How and when are variables referenced in Delphi's anonymous methods captured?
This was prompted by How to compare TFunc/TProc containing function/procedure of object? 这是由如何比较TFunc / TProc包含对象的功能/过程? , 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. 由于我没有博客发布这个问题,我将在这里提出这个问题并回答。
Question : When and how are variables referenced in Delphi's anonymous methods captured? 问题 :Delphi的匿名方法中何时以及如何引用变量?
Example: 例:
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;
Both ShowMessage
are going to show 2
. 两个ShowMessage
都将显示2
。 Why? 为什么? How does V
get captured and when? 如何捕获V
以及何时捕获?
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. 当你有一个类似问题的函数,你有一个访问局部变量的匿名方法时,Delphi似乎创建了一个TInterfacedObject后代,它捕获所有基于堆栈的变量,因为它是自己的公共变量。 Using Barry's trick to get to the implementing TObject and a bit of RTTI we can see this whole thing in action. 使用Barry的技巧来实现TObject和一些RTTI,我们可以看到整个事情的实际应用。
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;
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. 该对象用作接口(IUnkown),因此它被引用计数。 Apparently the same object captures all used variables AND defines all the anonymous methods. 显然,同一个对象捕获所有使用的变量并定义所有匿名方法。
This should answer both "When" and "How". 这应该回答“何时”和“如何”。
Here's the code I used to investigate. 这是我用来调查的代码。 Put a TButton on a blank form, this should be the whole unit. 把TButton放在一个空白表格上,这应该是整个单位。 When you press the button you'll see the following on screen, in sequence: 当您按下按钮时,您将在屏幕上按顺序看到以下内容:
TForm25.Button1Click$ActRec: TInterfacedObject
: This shows the object behind the implementation, it's derived from TInterfacedObject TForm25.Button1Click$ActRec: TInterfacedObject
:这显示了实现背后的对象,它来自TInterfacedObject OnStack:string
: RTTI discovers this field on that object. OnStack:string
:RTTI在该对象上发现此字段。 Self: TForm25
: RTTI discovers this field on that object. Self: TForm25
:RTTI在该对象上发现此字段。 It's used to get the value of ClasVar
它用于获取ClasVar
的值 FRefCount:Integer
- this comes from TInterfacedObject FRefCount:Integer
- 来自TInterfacedObject Class Var
- result of ShowMessage. Class Var
- ShowMessage的结果。 On Stack
- result of ShowMessage. On Stack
- ShowMessage的结果。 Here's the code: 这是代码:
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.
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.