Anonymous methods - capture variable capture and value

Below is SSCCE, based on an example in the "Anonymous Methods" section of Part 1 of Chris Rolliston's excellent Delphi XE2 Foundations book, about the idea of ​​a variable capture (any errors in it completely depend on me).

It works exactly as I expected, registering 666, 667, 668, 669 with successive clicks of the BtnInvoke Button. In particular, this illustrates well how the captured version of the local variable i is saved after btnSetUpClick exits.

So far so good. The problem I'm asking about is not in this code as such, but what Allen Bauer's blog post says here:

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

Now I know better than arguing with the boss, so I'm sure I miss the point about the difference he draws between variable capture and value capture. To my simple way of looking at this, my CR-based example captures the value of I, capturing I as a variable.

So my question is exactly what Mr. Bauer is trying to draw, exactly?

(By the way, despite the fact that I watched the Delphi SO section daily for 9 months, I'm still not completely clear if this is q on the topic. If not, my apologies and I will remove it.)

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; 
+6
delphi delphi-xe2
source share
2 answers

Variable capture and capture of values ​​is quite simple. Suppose two anonymous methods commit the same variable. Like this:

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

There is one variable that is captured by both methods. Exit:

 0
 one
 2

This is a variable capture. If the value was captured, but it is not, it can be assumed that the two methods will have separate variables that start with the value 0. And both functions can output 0.

In your example, it is assumed that P1 captures the value 41 , and P2 captures the value 665 . But this does not happen. There is exactly one variable. It is shared between the procedure declaring it and the anonymous methods that capture it. He lives as long as all its members live. Changes to a variable made by one side are visible to everyone else because there is exactly one variable.


Therefore, it is not possible to fix the value. To get this behavior, you need to copy the value into a new variable and capture this new variable. This can be done, for example, using the parameter.

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

This will copy the value of i into the new variable, the procedure parameter and write it. Subsequent changes to i do not affect P3 , because the captured variable is local to P3 .

+9
source share

Let's clarify things a bit; internally, any data captured by an anonymous method is an instance field of a hidden object and should be called a variable as such; but there may be different cases of capturing a variable.

Consider the code example:

 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; 

You can see that logically Proc1 (in Test1 ) captures the value of I , and Proc2 (in Test2 ) captures the reference to I

If you look under the hood, you will notice that the variable I in Test1 is a simple local stack variable, and Proc1 accesses the instance field of the hidden object (using the instance reference and field offset); we have two different variables (one on the stack and the other on the heap).

Test2 generally does not have a stack-based variable I , only an instance field of a hidden object; both Test2 and Proc2 access the same variable by reference to the instance (and field offset); we have one heap variable.

+5
source share

All Articles