Delphi实现匿名方法的一个有趣方面 / closures是捕获例程本地变量状态的能力 从中调用anon方法。这在Marco Cantu的书中有很好的描述(Delphi 2009?)。
我的问题是,有没有办法同样捕获变量的值 哪些是匿名方法本身的本地?
为了说明我的意思,请考虑以下代码段, 哪个不做我的意思:
type
TMemoProc = reference to procedure(Memo: TMemo; StepNo : Integer);
TForm1 = class(TForm)
[...]
private
procedure Process(Memo: TMemo; StepNo: Integer);
public
{ Public declarations }
MemoProc : TMemoProc;
procedure CreateMemoProc;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
i: Integer;
begin
for i := 1 to 3 do
MemoProc(Memo1, i);
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
CreateMemoProc;
MemoProc(Memo1, 0);
end;
procedure TForm1.Process(Memo : TMemo; StepNo : Integer);
var
Count : Integer;
begin
if StepNo = 0 then
Count := 1
else
Inc(Count);
Memo.Lines.Add(Self.Name + ' ' +Format('stepno: %d, count: %d', [StepNo, Count]));
end;
procedure TForm1.CreateMemoProc;
begin
MemoProc :=
procedure (Memo : TMemo; StepNo : Integer)
var
Count : Integer;
begin
if StepNo = 0 then
Count := 1
else
Inc(Count);
Memo.Lines.Add(Format('stepno: %d, count: %d', [StepNo, Count]));
end;
end;
单击Button1几次,插入的数字也就不足为奇了 改变,正如你所期望的那样,事实上Delphi没有隐式初始化 堆栈上的简单变量。
但我想知道是否有人可以想到一种巧妙的方法 在它的调用之间捕获anon方法的局部变量。
Fwiw,我之所以想知道这是因为有用例 通常需要对anon方法进行两次调用,一次要做一些 初始化和一个迭代,以及最简洁的编码方式 如果anon方法的局部变量可能以某种方式被捕获。
我想到的一种用例是编写一些通用代码来迭代数据集,在这里可以方便地指定一个布尔函数,该函数通过引用行的字段值来确定特定数据集行是否应该是处理和另一个定义当布尔函数返回true时应该对该行做什么。
在传统实现中,可以将字段指定为DataSet.FieldByName('xxx'),但是涉及许多字段和/或许多行时,这可能非常低效。
因此,我的q背后的议程基本上是能够指定anon方法的初始化调用中涉及的字段,然后直接使用它们(如DataSet11FieldCatflap,而不是DataSet.FieldByName('Catflap')。我已经考虑使用容器化的字段列表在初始化调用和迭代调用之间进行,但它似乎对我“不满意”。
答案 0 :(得分:2)
唯一可以捕获变量的是匿名方法。您希望有两个捕获相同变量的匿名方法。这意味着您需要在单个方法中创建匿名方法,以便匿名方法可以捕获相同的局部变量。像这样:
{$APPTYPE CONSOLE}
uses
System.SysUtils;
procedure PerformCapture(InitialValue: Integer; out Getter: TFunc<Integer>;
out Setter: TProc<Integer>);
var
Value: Integer;
begin
Value := InitialValue;
Getter := function: Integer
begin
Result := Value;
end;
Setter := procedure(AValue: Integer)
begin
Value := AValue;
end;
end;
var
Getter: TFunc<Integer>;
Setter: TProc<Integer>;
begin
PerformCapture(42, Getter, Setter);
Writeln(Getter());
Setter(666);
Writeln(Getter());
Setter(-17);
Writeln(Getter());
Readln;
end.
<强>输出强>
42 666 -17
显然我已经编写了非常简单的匿名方法作为Getter
和Setter
返回,但你可以自由地编写任意复杂的方法。实际上,您不需要直接在Getter
和Setter
匿名方法中实现所有内容。您可以将方法转发到代码的其他部分,传递捕获的变量。
匿名方法非常适合简单的状态捕获。但是,您不必使用匿名方法来将状态从一个方法调用传递到下一个方法。您可以使用更传统的方法,例如包含状态的类。您可能会比较这两种可能的方法,以便选择导致更清晰编码的机制。
答案 1 :(得分:2)
匿名方法从其封闭范围捕获局部变量的状态。如果方法的实现需要更多状态,则在封闭范围内声明更多局部变量,即使它们仅在匿名方法的范围内使用过。
在您的情况下,这意味着在Count
方法中声明和初始化CreateMemoProc
,而不是在MemoProc
中存储的匿名方法内。
一般来说,将变量声称看似错误的范围可能会造成难看和混乱。为避免这种情况,您可以使用方法工厂,其唯一的工作是生成匿名方法实例。那么关于变量声明的地方的任何奇怪都限于那个函数,所以目的很明确。你已经在CreateMemoProc
已经有了这个,但是如果它取决于我,它将是一个独立的函数而不是形式的方法:
function CreateMemoProc: TMemoProc;
var
Count: Integer;
begin
Count := 0;
Result := procedure (Memo : TMemo; StepNo : Integer)
begin
if StepNo = 0 then
Count := 1
else
Inc(Count);
Memo.Lines.Add(Format('stepno: %d, count: %d', [StepNo, Count]));
end;
end;
Count
变量是CreateMemoProc
的本地变量,它在匿名方法中使用,匿名方法捕获它。 CreateMemoProc
的每次调用都会创建Count
的新副本,因此每个匿名方法都会知道它被调用了多少次。
但我觉得您并不想在方法中包含StepNo
参数。相反,您希望该方法“知道”是否已经调用它。在这种情况下,我们可以使用标志增加Count
:
type
TMemoProc = reference to procedure(Memo: TMemo);
function CreateMemoProc: TMemoProc;
var
Count: Integer;
Initialized: Boolean;
begin
Count := 0;
Initialized := False;
Result := procedure(Memo: TMemo)
begin
if not Initialized then begin
Count := 0;
Initialized := True;
end;
Inc(Count);
Memo.Lines.Add(Format('count: %d', [Count]));
end;
end;