C# 如何|在何处存储闭合变量?

C# 如何|在何处存储闭合变量?,c#,linq,clr,closures,C#,Linq,Clr,Closures,这是一个基于Eric Lippert文章的问题。 这是一个很好的读物,Eric解释了为什么在这段代码之后,所有func将返回v中的last值: var funcs = new List<Func<int>>(); foreach (var v in values) { funcs.Add(() => v); } 现在我的问题是如何以及在哪里存储这些捕获的“v2”变量。在我对堆栈的理解中,所有这些v2变量都将占用同一块内存 我的第一个想法是拳击,每个

这是一个基于Eric Lippert文章的问题。
这是一个很好的读物,Eric解释了为什么在这段代码之后,所有func将返回v中的last值:

 var funcs = new List<Func<int>>();
 foreach (var v in values)
 {
    funcs.Add(() => v);
 }
现在我的问题是如何以及在哪里存储这些捕获的“v2”变量。在我对堆栈的理解中,所有这些v2变量都将占用同一块内存


我的第一个想法是拳击,每个func成员都保留一个对拳击v2的引用。但这并不能解释第一个案例

通常,变量
v2
将在堆栈中找到的代码块开始处分配一些空间。在代码块的末尾(即迭代的末尾),堆栈被卷回(我描述的是逻辑场景,而不是优化的实际行为)。因此,每个
v2
实际上是与上一次迭代不同的
v2
,尽管它最终会占用相同的内存位置

但是编译器发现,
v2
正被lambda创建的匿名函数使用。编译器所做的是
v2
变量。编译器创建了一个新类,该类有一个Int32字段来保存
v2
的值,但未在堆栈上分配位置。它还使匿名函数成为这种新类型的方法。(为了简单起见,我将给这个未命名的类命名,让我们称它为“Thing”)

现在,在每一次迭代中,都会创建一个新的实例“Thing”,当
v2
被分配它的Int32字段时,它实际上被分配的不仅仅是堆栈内存中的一个点。匿名函数表达式(lambda)现在将返回一个具有非空实例对象引用的委托,该引用将指向“Thing”的当前实例


当调用匿名函数的委托时,它将作为“Thing”实例的实例方法执行。因此,
v2
可作为成员字段使用,并将在迭代过程中为其提供值。创建了“Thing”实例。

进一步了解Neil和Anthony的答案,下面是两种情况下可能自动生成的代码示例

(请注意,这只是为了演示原理,实际编译器生成的代码与此不完全相同。如果您想查看真实代码,可以使用Reflector进行查看。)


好的,在阅读了我自己的问题之后,我想它的解释是:在第一个版本中,
v
被装箱一次,引用被重复使用。但我想看到一个更权威的答案。
 foreach (var v in values)
 {
    int v2 = v;
    funcs.Add(() => v2);
 }
// first loop
var captures = new Captures();
foreach (var v in values)
{
    captures.Value = v;
    funcs.Add(captures.Function);
}

// second loop
foreach (var v in values)
{
    var captures = new Captures();
    captures.Value = v;
    funcs.Add(captures.Function);
}

// ...

private class Captures
{
    public int Value;

    public int Function()
    {
        return Value;
    }
}