匿名方法的范围

jpf*_*ius 10 delphi scope anonymous-methods delphi-2009

匿名方法的一个好处是我可以使用调用上下文中的本地变量.有什么理由说这不适用于输出参数和功能结果吗?

function ReturnTwoStrings (out Str1 : String) : String;
begin
  ExecuteProcedure (procedure
                    begin
                      Str1 := 'First String';
                      Result := 'Second String';
                    end);
end;
Run Code Online (Sandbox Code Playgroud)

当然是非常人为的例子,但我遇到了一些有用的情况.

当我尝试编译它时,编译器抱怨他"无法捕获符号".此外,当我尝试这样做时,我收到了一个内部错误.

编辑我刚刚意识到它适用于普通参数

... (List : TList)
Run Code Online (Sandbox Code Playgroud)

这不像其他案件那样有问题吗?谁保证只要执行匿名方法,引用仍指向活动对象?

Bar*_*lly 20

无法捕获Var和out参数以及Result变量,因为无法静态验证此操作的安全性.当Result变量属于托管类型(如字符串或接口)时,存储实际上由调用者分配,并且对此存储的引用作为隐式参数传递; 换句话说,Result变量(取决于其类型)就像out参数.

由于Jon提到的原因,无法验证安全性.由匿名方法创建的闭包可以比创建它的方法激活更长,并且可以类似地比调用创建它的方法的方法的激活更长.因此,捕获的任何var或out参数或Result变量都可能最终成为孤立状态,并且将来从闭包内部对它们的任何写入都会破坏堆栈.

当然,Delphi不在托管环境中运行,并且它没有与C#相同的安全限制.语言可以让你做你想做的事.但是,在出错的情况下,很难诊断出错误.不良行为将表现为常规变化值中的局部变量,没有可见的近因; 如果从另一个线程调用方法引用会更糟糕.

调试起来相当困难.即使硬件内存断点也会是一个相对较差的工具,因为堆栈经常被修改.在遇到另一个断点时(例如,在方法输入时),需要有条件地打开硬件存储器断点.Delphi调试器可以做到这一点,但我猜测大多数人都不知道这种技术.

更新:关于您的问题的添加,按值传递实例引用的语义在包含闭包的方法之间没有什么不同(并且捕获参数0和不包含闭包的方法.这两种方法都可以保留对通过值传递的参数;不捕获参数的方法可以简单地将引用添加到列表中,或者将其存储在私有字段中.

由参考传递的参数的情况不同,因为呼叫者的期望是不同的.一个程序员这样做:

procedure GetSomeString(out s: string);
// ...
GetSomeString(s);
Run Code Online (Sandbox Code Playgroud)

如果GetSomeString保持对s传入的变量的引用,那将会非常惊讶.另一方面:

procedure AddObject(obj: TObject);
// ...
AddObject(TObject.Create);
Run Code Online (Sandbox Code Playgroud)

AddObject保留引用并不奇怪,因为它的名字暗示它正在将参数添加到某个有状态存储中.该有状态存储是否为闭包形式是该AddObject方法的实现细节.

  • Alexander,out和var参数通过引用传递.这意味着捕获变量只会捕获对存储位置的引用,而不是位置本身.编译器无法捕获var或out参数后面的位置,因为变量捕获是通过将存储从堆栈移动到堆上来实现的,这需要在内部重写声明该位置的方法.由于任何代码都可以使用var或out参数调用方法,包括来自其他语言的方法,因此重写该方法为时已晚.它必须在编译时发生. (3认同)

Jer*_*ers 6

问题是你的Str1变量不是由ReturnTwoStrings"拥有"的,所以你的匿名方法无法捕获它.

它无法捕获它的原因是,编译器不知道最终所有者(调用堆栈中的某个地方调用ReturnTwoStrings),因此它无法确定从何处捕获它.

编辑:(Smasher评论后添加)

匿名方法的核心是它们捕获变量(而不是它们的值).

Allen Bauer(CodeGear)在他的博客中解释了有关变量捕获的更多信息.

关于绕过问题C#问题.