匿名方法是如何在幕后实现的?
How are anonymous methods implemented under the hood?
是否 Delphi "instantiate" 每个匿名方法(如对象)?如果是,Delphi 何时创建此实例,最重要的是,何时 Delphi免费吗?
因为匿名方法也会捕获外部变量并延长它们的生命周期,所以知道这些变量什么时候会"released"从内存中是很重要的。
在另一个匿名方法中声明一个匿名方法可能有哪些缺点。
是否可以循环引用?
匿名方法作为接口实现,方法名为 Invoke,该方法具有与匿名方法声明相同的签名。所以从技术上讲,reference to function(a: Integer): string
与此接口二进制兼容:
X = interface
function Invoke(a: Integer): string;
end;
几个版本以前甚至可以调用 .Invoke 匿名方法,但编译器现在阻止了。
当您在内联声明匿名方法时,编译器会在例程的序言中创建一些代码,以确保捕获的任何变量都不会存在于堆栈上,而是存在于堆上(这也是您为什么无法在调试期间检查任何捕获的变量,因为不幸的是它缺少该信息)。编译器在该接口后面创建一个 class,其中包含与您正在捕获的变量同名的字段(有关详细信息,请参阅 this blog article)。
至于循环引用,是的。请注意,例如,当您捕获一个接口(或在启用了对象 ARC 的下一代平台的情况下的对象)时,您可能会导致循环引用,从而导致内存泄漏。
同样有趣的是,如果您在同一个例程中有多个匿名方法,它们都由同一个编译器生成的对象支持。这可能会导致可能出现内存泄漏的另一种情况,因为一个匿名方法也可能捕获另一个并创建另一个循环引用。
匿名方法作为接口实现。这篇文章很好地解释了编译器是如何完成的: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.
编译器在后台创建一个隐藏的 class 来实现匿名方法接口。 class 包含作为数据成员的任何捕获的变量。当 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;
是否 Delphi "instantiate" 每个匿名方法(如对象)?如果是,Delphi 何时创建此实例,最重要的是,何时 Delphi免费吗?
因为匿名方法也会捕获外部变量并延长它们的生命周期,所以知道这些变量什么时候会"released"从内存中是很重要的。
在另一个匿名方法中声明一个匿名方法可能有哪些缺点。 是否可以循环引用?
匿名方法作为接口实现,方法名为 Invoke,该方法具有与匿名方法声明相同的签名。所以从技术上讲,reference to function(a: Integer): string
与此接口二进制兼容:
X = interface
function Invoke(a: Integer): string;
end;
几个版本以前甚至可以调用 .Invoke 匿名方法,但编译器现在阻止了。
当您在内联声明匿名方法时,编译器会在例程的序言中创建一些代码,以确保捕获的任何变量都不会存在于堆栈上,而是存在于堆上(这也是您为什么无法在调试期间检查任何捕获的变量,因为不幸的是它缺少该信息)。编译器在该接口后面创建一个 class,其中包含与您正在捕获的变量同名的字段(有关详细信息,请参阅 this blog article)。
至于循环引用,是的。请注意,例如,当您捕获一个接口(或在启用了对象 ARC 的下一代平台的情况下的对象)时,您可能会导致循环引用,从而导致内存泄漏。
同样有趣的是,如果您在同一个例程中有多个匿名方法,它们都由同一个编译器生成的对象支持。这可能会导致可能出现内存泄漏的另一种情况,因为一个匿名方法也可能捕获另一个并创建另一个循环引用。
匿名方法作为接口实现。这篇文章很好地解释了编译器是如何完成的: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.
编译器在后台创建一个隐藏的 class 来实现匿名方法接口。 class 包含作为数据成员的任何捕获的变量。当 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;