Delphi封闭和“旧式”对象类型

时间:2016-11-23 22:19:19

标签: delphi closures

使用匿名函数我发现有时编译器会抛出以下错误: E2555当我尝试使用对象的某个字段时,无法捕获符号“Self”。 我还注意到这个错误似乎与这样一个事实有关,即该方法所属的类型是用“object”关键字声明的:

MyType = object()
    field: integer;
...
end; 

MyType.Method1()
begin
    p := procedure
    begin
        // do something with field
    end;
end;

但是当使用“class”关键字声明类型时,它似乎工作正常。

我知道为了防止编译错误我可以制作所需字段的本地副本并在匿名函数中使用它们,但只是为了确定 - 是“对象”类型导致编译器错误的原因是什么原因?

提前致谢

2 个答案:

答案 0 :(得分:3)

由于Turbo Pascal object一直被弃用,因此新语言功能不支持object是合理的。

没有必要进一步了解。由于您要维护遗留代码,我不希望您引入匿名方法等新语言功能。一旦开始介绍这些语言功能,这就不再像遗留代码维护一样,将代码重新分解为object等遗留语言功能是合理的。

话虽如此,我确实注意到捕获的相同限制适用于高级记录的方法。

type
  TProc = reference to procedure;
  TRecord = record
    procedure Foo;
  end;

procedure TRecord.Foo;
var
  P: TProc;
begin
  P :=
    procedure
    begin
      Foo;
    end;
end;

无法编译错误:

  

E2555无法捕捉符号'Self'

为什么此代码会失败,即使高级记录是完全支持的现代功能?

我对此没有解释,文档也没有说清楚。一个合理的解释是记录是价值类型。捕获局部变量时,它将从堆栈分配变量提升为内部创建的类所拥有的变量。当Self是对类实例的引用时,Self可以实现。但是当Self是一个类似记录的值时,提升记录为时已晚。

或者它可能更平淡无奇。也许设计师只是实现了最重要的用例(为一个类捕获Self),并省略了不太广泛使用的权宜之计。令人沮丧的是,文档似乎没有为可以捕获和不捕获的内容提供任何规则。

答案 1 :(得分:3)

正如大卫正确分析的那样,因为你的案例中的Self是一个值,而不是一个引用。它不能移动到内部创建的类 - 任何方法参数都是记录的情况也是如此。出于同样的原因,它们也无法被捕获。

对于参数,我通常将它们复制到正在捕获的局部变量中。 在Selfrecord中捕获object也可以这样做。

但是,如果将其捕获为值,则会获得一个副本,稍后调用该闭包可能会出现“错误”状态,因为它捕获了一个副本。为了使其工作类似,您必须捕获对Self的引用,但是对于值类型,您无法保证在调用闭包时此引用仍然有效。

您可以在以下代码中看到:

program Project1;

{$APPTYPE CONSOLE}

uses
  SysUtils;

type
  TProc = reference to procedure;
  PRecord = ^TRecord;
  TRecord = object
    y: Integer;
    procedure Foo;
    function GetProc: TProc;
  end;

procedure TRecord.Foo;
begin
  Writeln(y);
end;

function TRecord.GetProc: TProc;
var
  this: PRecord;
begin
  this := @Self;
  Result :=
    procedure
    begin
      this.Foo;
    end;
end;

procedure Nested(var p: TProc);
var
  r: TRecord;
begin
  p := r.GetProc();
  r.y := 0;
  p();
  r.y := 32;
  p();
end;

procedure Main;
var
  p: TProc;
begin
  Nested(p);
  p(); // <- wrong value because PRecord not valid anymore
end;

begin
  Main;
end.

如果您要捕获TRecord,它会执行它捕获的本地副本 - 您可以看到它会一直打印0。