Delphi"实例化"每个匿名方法(如一个对象)?,如果是这样的话Delphi什么时候创建这个实例,最重要的是Delphi何时释放它?
因为匿名方法还可以捕获外部变量并延长其使用寿命,所以了解这些变量何时会被释放"这一点非常重要。从记忆中。
在另一个匿名方法中声明匿名方法有什么可能的缺点。 是否可以进行循环引用?
答案 0 :(得分:15)
匿名方法作为接口实现。本文很好地解释了编译器如何完成它:Anonymous methods in Delphi: the internals。
本质上,编译器生成的接口有一个名为Invoke
的方法,其后面是您提供的匿名方法。
捕获的变量与捕获它们的任何匿名方法具有相同的生命周期。匿名方法是一个接口,其生命周期由引用计数管理。因此,捕获的变量生命延长与捕获它们的匿名方法一样长。
正如可以使用接口创建循环引用一样,同样可以使用匿名方法创建循环引用。这是我可以构建的最简单的演示:
uses
System.SysUtils;
procedure Main;
var
proc: TProc;
begin
proc :=
procedure
begin
if Assigned(proc) then
Beep;
end;
end;
begin
ReportMemoryLeaksOnShutdown := True;
Main;
end.
在幕后,编译器会创建一个实现匿名方法接口的隐藏类。该类包含任何捕获的变量作为数据成员。分配proc
时,会增加实现实例的引用计数。由于proc
由实现实例拥有,因此该实例已引用自身。
为了使这个更清楚一点,这个程序提出了相同的问题,但是在接口方面重新构建:
uses
System.SysUtils;
type
ISetValue = interface
procedure SetValue(const Value: IInterface);
end;
TMyClass = class(TInterfacedObject, ISetValue)
FValue: IInterface;
procedure SetValue(const Value: IInterface);
end;
procedure TMyClass.SetValue(const Value: IInterface);
begin
FValue := Value;
end;
procedure Main;
var
intf: ISetValue;
begin
intf := TMyClass.Create;
intf.SetValue(intf);
end;
begin
ReportMemoryLeaksOnShutdown := True;
Main;
end.
可以通过明确清除自引用来打破循环。在匿名方法示例中,如下所示:
procedure Main;
var
proc: TProc;
begin
proc :=
procedure
begin
if Assigned(proc) then
Beep;
end;
proc := nil;
end;
接口变体的等价物是:
procedure Main;
var
intf: ISetValue;
begin
intf := TMyClass.Create;
intf.SetValue(intf);
intf.SetValue(nil);
end;
答案 1 :(得分:8)
匿名方法作为接口实现,其方法名为Invoke,其签名与匿名方法声明相同。因此从技术上讲,reference to function(a: Integer): string
与此接口是二进制兼容的:
X = interface
function Invoke(a: Integer): string;
end;
在几个版本之前,甚至可以在匿名方法上调用.Invoke,但编译器现在可以阻止它。
当您声明内联的匿名方法时,编译会在例程的序言中创建一些代码,以确保捕获的任何变量不会在堆栈上但在堆上(这也是您无法检查的原因)调试期间捕获的任何变量,因为遗憾的是缺少该信息)。编译器在该接口后面创建一个类,其中的字段与您捕获的变量具有相同的名称(有关详细信息,请参阅this blog article)。
至于循环引用,是的。请注意,例如,当您捕获一个接口(或者对于启用了对象的ARC的nextgen平台的对象)时,可能会导致循环引用导致内存泄漏。
同样有趣的是,如果在同一例程中有多个匿名方法,则它们都由相同的编译器生成对象支持。这可能会导致出现内存泄漏的另一种情况,因为一个匿名方法也可能捕获另一个并创建另一个循环引用。