在Delphi的匿名方法中引用的变量和何时被捕获? [英] How and when are variables referenced in Delphi's anonymous methods captured?
问题描述
这是如何比较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 TInterfacedObjectOnStack:string
: RTTI discovers this field on that object.Self: TForm25
: RTTI discovers this field on that object. It's used to get the value ofClasVar
FRefCount:Integer
- this comes from TInterfacedObjectClass 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屋!