匿名方法 - 变量捕获与值捕获

Mar*_*ynA 6 delphi delphi-xe2

下面是一个SSCCE,它基于Chris Rolliston优秀的Delphi XE2基础知识书第1部分的匿名方法部分中的一个例子,关于变量捕获的概念(其中的任何错误完全取决于我).

它完全按照我的预期工作,在连续点击BtnInvoke按钮时记录666,667,668,669.特别是它很好地说明了在btnSetUpClick退出后很长时间内捕获的局部变量版本I的持续时间.

到现在为止还挺好.我问的问题不在于这个代码本身,而是在Allen Bauer的博客中所说的:

http://blogs.embarcadero.com/abauer/2008/10/15/38876

现在,我知道最好不要和老板争论,所以我确信我错过了他在变量捕获和价值捕获之间区分的重点.以我简单的方式查看它,我的基于CR的示例通过捕获I作为变量来捕获I的值.

所以,我的问题是,鲍尔先生试图绘制的区别究竟是什么?

(顺便说一句,尽管每天都会看到SO的Delphi部分超过9个月,但我仍然不完全清楚这个q是否在主题上.如果没有,我道歉并且我会把它取下来.)

type
  TAnonProc = reference to procedure;

var
  P1,
  P2 : TAnonProc;

procedure TForm2.Log(Msg : String);
begin
  Memo1.Lines.Add(Msg);
end;

procedure TForm2.btnSetUpClick(Sender: TObject);
var
  I : Integer;
begin
  I := 41;
  P1 := procedure
    begin
      Inc(I);
      Log(IntToStr(I));
    end;

  I := 665;
  P2 := procedure
    begin
      Inc(I);
      Log(IntToStr(I));
    end;
end;

procedure TForm2.btnInvokeClick(Sender: TObject);
begin
  Assert(Assigned(P1));
  Assert(Assigned(P2));

  P1;
  P2;
end;
Run Code Online (Sandbox Code Playgroud)

Dav*_*nan 10

变量捕获与值捕获非常简单.让我们假设两个匿名方法捕获相同的变量.像这样:

Type
  TMyProc = reference to procedure;
var
  i: Integer;
  P1, P2: TMyProc;
....
i := 0;
P1 := procedure begin Writeln(i); inc(i); end;
P2 := procedure begin Writeln(i); inc(i); end;
P1();
P2();
Writeln(i);
Run Code Online (Sandbox Code Playgroud)

两种方法都捕获一个变量.输出是:

0
1
2

这是一个变量的捕获.如果捕获的值不是,那么可以想象这两个方法将具有以值0开始的单独变量.并且两个函数都可以输出0.

在您的示例中,您应该想象P1捕获值41P2捕获值665.但这不会发生.只有一个变量.它在声明它的过程和捕获它的匿名方法之间共享.只要所有分享它的各方都活着,它就会存在.所有其他人都可以看到由一方对变量进行的修改,因为只有一个变量.


因此,无法捕获值.要获得您需要将值复制到新变量并捕获该新变量的行为.例如,可以使用参数来完成.

function CaptureCopy(Value: Integer): TMyProc;
begin
  Result := procedure begin Writeln(Value); end;
end;

...
P3 := CaptureCopy(i);
Run Code Online (Sandbox Code Playgroud)

这会将值复制i到新变量,过程参数和捕获它.后续更改iP3因为捕获的变量是本地的没有影响P3.


klu*_*udg 5

让我们澄清一下事情; 在内部,匿名方法捕获的任何数据都是隐藏对象实例的字段,因此应该被称为变量 ; 但是可以有不同的捕获变量的情况.

考虑示例代码:

type
  TMyProc = reference to procedure;

function CaptureValue(Value: Integer): TMyProc;
begin
  Result := procedure begin Inc(Value); Writeln(Value); end;
end;

procedure Test1;
var
  Proc1: TMyProc;
  I: Integer;

begin
  I:= 32;
  Proc1:= CaptureValue(I);
  Proc1();
  Writeln(I);     // 32
end;

procedure Test2;
var
  Proc2: TMyProc;
  I: Integer;

begin
  I:= 32;
  Proc2:= procedure begin Inc(I); Writeln(I); end;
  Proc2();
  Writeln(I);    // 33
end;
Run Code Online (Sandbox Code Playgroud)

你可以看到,在逻辑上Proc1(中Test1)捕获I ,而Proc2(中Test2)捕捉一个参考I.

如果你仔细观察,你会发现变量Iin Test1是一个普通的基于本地堆栈的变量,同时Proc1访问一个隐藏对象实例的字段(使用对该实例的引用和字段的偏移量); 我们有两个不同的变量(一个在堆栈上,另一个在堆上).

Test2根本没有基于堆栈的I变量,只有隐藏对象实例的字段; 两者Test2Proc2通过引用实例(和字段的偏移量)访问相同的变量; 我们有一个堆变量.