封闭变量如何|在哪里存储?

发布于 2024-08-11 08:25:01 字数 650 浏览 2 评论 0原文

这是基于文章 “关闭被认为有害的循环变量” 作者:Eric Lippert。
这是一本很好的书,Eric 解释了为什么在这段代码之后所有 funcs 将返回 v 中的 last 值:

 var funcs = new List<Func<int>>();
 foreach (var v in values)
 {
    funcs.Add(() => v);
 }

正确的版本如下所示:

 foreach (var v in values)
 {
    int v2 = v;
    funcs.Add(() => v2);
 }

现在我的问题是那些捕获的 'v2 如何以及在哪里' 存储的变量。根据我对堆栈的理解,所有这些 v2 变量都会占用同一块内存。

我的第一个想法是装箱,每个 func 成员都保留对装箱 v2 的引用。但这并不能解释第一种情况。

This is a question based on the article "Closing over the loop variable considered harmful" by Eric Lippert.
It is a good read, Eric explains why after this piece of code all funcs will return the last value in v:

 var funcs = new List<Func<int>>();
 foreach (var v in values)
 {
    funcs.Add(() => v);
 }

And the correct version looks like:

 foreach (var v in values)
 {
    int v2 = v;
    funcs.Add(() => v2);
 }

Now my question is how and where are those captured 'v2' variables stored. In my understanding of the stack, all those v2 variables would occupy the same piece of memory.

My first thought was boxing, each func member keeping a reference to a boxed v2. But that would not explain the first case.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

冷︶言冷语的世界 2024-08-18 08:25:01

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

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

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

当调用匿名函数的委托时,它将作为“Thing”实例的实例方法执行。因此,v2 可用作成员字段,并且将在创建“Thing”实例的迭代期间为其赋予值。

Ordinarily the variable v2 would be allocated some space on the stack at the start of the code block its found in. At the end of the code block (i.e. the end of the iteration) the stack is wound back (I'm describing the logical scenario not an optimised actual behaviour). Hence each v2 is in effect a different v2 from the previous iteration although its true that it would end up occupying the same memory location.

However the compiler spots that v2 is being used by an anonymous function created by the lambda. What the compiler does is hoist the v2 variable. The compiler creates a new class that has an Int32 field to hold the value of v2, it is not allocated a place on the stack. It also makes the anonymous function a method of this new type. (For simplicity I'll give this un-named class a name, lets call it "Thing").

Now in each iteration a new instance of "Thing" is created and when v2 is assigned its the Int32 field which is actually assigned not just a point in stack memory. The anonymous function expression (the lambda) will now return a delegate which has non-null instance object reference, this reference will be to the current instance of "Thing".

When the delegate for anonymous function is invoked it will execute as an instance method of a "Thing" instance. Hence v2 is available as a member field and will have the value give it during the iteration this instance of "Thing" was created.

酷到爆炸 2024-08-18 08:25:01

除了尼尔和安东尼的答案之外,下面是在这两种情况下可能自动生成的代码示例。

(注意,这只是为了演示原理,实际编译器生成的代码不会完全像这样。如果你想看到真正的代码,那么你可以使用 Reflector 看一下。)

// 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;
    }
}

Further to the answers from Neil and Anthony, here's an example of the code that might be auto-generated in both cases.

(Note that this is only to demonstrate the principle, the actual compiler-generated code won't look exactly like this. If you want to see the real code then you can take a look using Reflector.)

// 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;
    }
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文