【问题标题】:Anonymous methods - variable capture versus value capture匿名方法 - 变量捕获与价值捕获
【发布时间】:2014-06-14 19:20:12
【问题描述】:

以下是基于第 1 部分的匿名方法部分中的示例的 SSCCE Chris Rolliston 的优秀 Delphi XE2 Foundations 书,关于变量的概念 捕获(其中的任何错误完全取决于我)。

它的工作原理完全符合我的预期,在连续点击 666、667、668、669 BtnInvoke 按钮。特别是它很好地说明了捕获的版本 局部变量 I 在 btnSetUpClick 退出后仍然存在很长时间。

到目前为止一切顺利。我要问的问题不是这段代码本身,而是 Allen Bauer 的博客中所说的:

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

现在,我知道最好不要和老板争论,所以我确定我没有抓住重点 他在变量捕获和价值捕获之间做出了区分。以我的简单方式 看看它,我的基于 CR 的示例通过将 I 捕获为变量来捕获 I 的值。

那么,我的问题是,Bauer 先生试图画出的区别究竟是什么?

(顺便说一句,尽管每天观看 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;

【问题讨论】:

    标签: delphi delphi-xe2


    【解决方案1】:

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

    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);
    

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

    0 1 2

    这是一个变量的捕获。如果该值被捕获,但事实并非如此,人们可能会想象这两种方法将具有单独的变量,它们都以值 0 开头。并且两个函数都可能输出 0。

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


    因此,无法捕获值。要获得类似的行为,您需要将值复制到新变量,并捕获该新变量。例如,这可以通过一个参数来完成。

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

    这会将i 的值复制到一个新变量中,即过程参数,并捕获它。对i 的后续更改对P3 没有影响,因为捕获的变量是P3 的本地变量。

    【讨论】:

    • 我还添加了一个价值捕获示例。我认为一种好的编程风格是始终明确说明如何引用变量。很多时候,如果值在范围的后期保持不变,那么您就可以通过引用捕获(当您实际上想要一个值捕获时)逃脱,但是如果您稍后在代码中添加一些更改值的内容,则存在潜在的风险。跨度>
    • @LURD 捕获始终是一个变量,而不是一个值。您的示例捕获了一个变量,因为当然可以做到这一点。没有价值捕获。发生的情况是,当您调用 CaptureValue 时,该值被复制到一个新变量中。我想你或许也应该解释一下。
    • @LURD 我做了进一步的编辑。我希望没关系。感谢您的意见。
    • 我收到了[dcc32 Error] Unit1.pas(54): E2010 Incompatible types: 'Integer' and 'TMyProc',我该如何解决这个问题?
    【解决方案2】:

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

    考虑示例代码:

    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;
    

    您可以看到,Proc1Test1)在逻辑上捕获I,而Proc2Test2)捕获引用 到I

    如果您深入了解,您会注意到Test1 中的变量I 是一个普通的基于本地堆栈的变量,而Proc1 访问隐藏对象实例的字段(使用对实例的引用和场偏移);我们有 2 个不同的变量(一个在堆栈上,另一个在堆上)。

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

    【讨论】:

    • +1。可惜只能接受一个答案。对我来说,一看到大卫的第一版答案,一分钱就掉了。
    猜你喜欢
    • 1970-01-01
    • 2012-11-01
    • 2019-03-09
    • 2010-10-29
    • 2021-06-08
    • 1970-01-01
    • 2013-08-04
    • 1970-01-01
    相关资源
    最近更新 更多