Hen*_*man 8 c# linq clr closures
这是一个基于Eric Lippert 撰写的文章"Closing over the loop variable related harm"的问题.
这是一个很好的阅读,Eric解释了为什么在这段代码之后所有的funcs将返回v中的最后一个值:
var funcs = new List<Func<int>>();
foreach (var v in values)
{
funcs.Add(() => v);
}
Run Code Online (Sandbox Code Playgroud)
正确的版本看起来像:
foreach (var v in values)
{
int v2 = v;
funcs.Add(() => v2);
}
Run Code Online (Sandbox Code Playgroud)
现在我的问题是那些捕获的'v2'变量存储的方式和位置.在我对堆栈的理解中,所有这些v2变量都会占用同一块内存.
我的第一个想法是拳击,每个func成员保持对盒装v2的引用.但这并不能解释第一种情况.
通常,变量v2
将在其找到的代码块的开头处在堆栈上分配一些空间.在代码块的末尾(即迭代的结束),堆栈被卷回(我正在描述逻辑场景)不是优化的实际行为).因此,每个v2
实际上都v2
与前一次迭代不同,尽管它最终会占据相同的内存位置.
但是编译器会发现v2
lambda创建的匿名函数正在使用它.什么编译器是葫芦的v2
变量.编译器创建一个新类,它具有一个Int32字段来保存值v2
,它没有在栈上分配一个位置.它还使匿名函数成为这种新类型的方法.(为简单起见,我将给这个未命名的类命名,我们称之为"Thing").
现在,在每次迭代中,都会创建一个新的"Thing"实例,并在v2
为其分配Int32字段时实际分配不仅仅是堆栈内存中的一个点.匿名函数表达式(拉姆达)现在将返回其具有非空实例对象引用一个委托,该参考将是到当前实例 "物"的.
当调用匿名函数的委托时,它将作为"Thing"实例的实例方法执行.因此v2
可以作为成员字段使用,并且在迭代期间将赋予它这个"Thing"实例的值.