在C#中的循环中捕获的变量
我遇到了一个关于C#的有趣问题。我的代码如下。
List<Func<int>> actions = new List<Func<int>>();
int variable = 0;
while (variable < 5)
{
actions.Add(() => variable * 2);
++ variable;
}
foreach (var act in actions)
{
Console.WriteLine(act.Invoke());
}
我希望它输出0、2、4、6、8。但是,它实际上输出了五个10。
看来这是由于所有涉及一个捕获变量的动作所致。结果,当他们被调用时,它们都具有相同的输出。
有没有办法在此限制中工作以使每个动作实例都有自己的捕获变量?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(11)
是的 - 播放循环内部变量的副本:
您可以将其视为C#编译器每次击中变量声明时都会创建“新”本地变量一样。实际上,它将创建适当的新闭合对象,如果您参考多个范围中的变量,它会变得复杂(在实现方面),但它有效:)
请注意,此问题更常见的发生是使用
for
或foreach
:有关此信息的更多详细信息,请参见C#3.0规格的7.14.4.4节,以及我的有关关闭的文章也有更多示例。
请注意,从C#5编译器及以后(即使指定较早版本的C#),
foreach
的行为也更改了,因此您不再需要进行本地副本。请参阅答案有关更多详细信息。Yes - take a copy of the variable inside the loop:
You can think of it as if the C# compiler creates a "new" local variable every time it hits the variable declaration. In fact it'll create appropriate new closure objects, and it gets complicated (in terms of implementation) if you refer to variables in multiple scopes, but it works :)
Note that a more common occurrence of this problem is using
for
orforeach
:See section 7.14.4.2 of the C# 3.0 spec for more details of this, and my article on closures has more examples too.
Note that as of the C# 5 compiler and beyond (even when specifying an earlier version of C#), the behavior of
foreach
changed so you no longer need to make local copy. See this answer for more details.我相信您所经历的是闭合的东西。您的lamba有一个引用一个变量,该变量在函数本身之外范围内进行了范围。在调用它之前,您的lamba不会被解释,并且一旦它将获得变量在执行时的值。
I believe what you are experiencing is something known as Closure http://en.wikipedia.org/wiki/Closure_(computer_science). Your lamba has a reference to a variable which is scoped outside the function itself. Your lamba is not interpreted until you invoke it and once it is it will get the value the variable has at execution time.
在幕后,编译器正在生成一个代表您方法呼叫的关闭的类。它使用循环的每个迭代使用闭合类的单个实例。该代码看起来像这样,这使您更容易查看为什么发生错误的原因:
这实际上不是您的示例中编译的代码,但是我已经检查了自己的代码,这看起来很像编译器实际生成的代码。
Behind the scenes, the compiler is generating a class that represents the closure for your method call. It uses that single instance of the closure class for each iteration of the loop. The code looks something like this, which makes it easier to see why the bug happens:
This isn't actually the compiled code from your sample, but I've examined my own code and this looks very much like what the compiler would actually generate.
这与循环无关。
触发此行为是因为您使用lambda表达式
()=&gt;变量 * 2
其中外部范围变量
实际上未在lambda的内部范围中定义。Lambda表达式(在C#3+中以及C#2中的匿名方法)仍然创建实际方法。将变量传递给这些方法涉及一些困境(按值传递?逐个引用?c#通过参考将带来 - 但这打开了另一个问题,参考可以超过实际变量)。 C#要解决所有这些困境的工作是创建一个新的助手类(“闭合”),其字段与lambda表达式中使用的局部变量相对应,以及与实际lambda方法相对应的方法。代码中对
变量
的任何更改实际上都会翻译为更改该clocreclass.variable
,因此您的wier loop不断更新
collecleclass.variable.variable
直到它到达10,然后您进行循环执行操作,所有操作都在相同的colleclaclass.variable
上进行操作。为了获得预期的结果,您需要在循环变量与正在关闭的变量之间创建一个分离。您可以通过引入另一个变量,即:
您也可以将闭合移动到创建此分隔的方法:
您可以将元用作lambda表达式(隐式闭合)
或实际助手类别实现:
无论如何:在任何情况下:“封闭”不是与环路< / strong>有关的概念,而是与匿名方法 / lambda表达式使用本地范围示波器变量的概念 - 尽管对环的某些不典型的使用表明了闭合陷阱。
This has nothing to do with loops.
This behavior is triggered because you use a lambda expression
() => variable * 2
where the outer scopedvariable
not actually defined in the lambda's inner scope.Lambda expressions (in C#3+, as well as anonymous methods in C#2) still create actual methods. Passing variables to these methods involve some dilemmas (pass by value? pass by reference? C# goes with by reference - but this opens another problem where the reference can outlive the actual variable). What C# does to resolve all these dilemmas is to create a new helper class ("closure") with fields corresponding to the local variables used in the lambda expressions, and methods corresponding to the actual lambda methods. Any changes to
variable
in your code is actually translated to change in thatClosureClass.variable
So your while loop keeps updating the
ClosureClass.variable
until it reaches 10, then you for loops executes the actions, which all operate on the sameClosureClass.variable
.To get your expected result, you need to create a separation between the loop variable, and the variable that is being closured. You can do this by introducing another variable, i.e.:
You could also move the closure to another method to create this separation:
You can implement Mult as a lambda expression (implicit closure)
or with an actual helper class:
In any case, "Closures" are NOT a concept related to loops, but rather to anonymous methods / lambda expressions use of local scoped variables - although some incautious use of loops demonstrate closures traps.
这样做的方法是将所需的值存储在代理变量中,并捕获该变量。
IE
The way around this is to store the value you need in a proxy variable, and have that variable get captured.
I.E.
是的,您需要在循环中范围
变量
将其传递给lambda:Yes you need to scope
variable
within the loop and pass it to the lambda that way:多线程(c#,。net 4.0
] :
是打印1,2,3,4,5的顺序。
目的
代码
The same situation is happening in multi-threading (C#, .NET 4.0].
See the following code:
Purpose is to print 1,2,3,4,5 in order.
The output is interesting! (It might be like 21334...)
The only solution is to use local variables.
正如其他人所说的那样,这与循环无关。这是c#中匿名功能正文中变量捕获机制的效果。
当您将lambda定义为示例时;
编译器生成一个容器类,例如&lt;&gt; c__displayClass0_0 for lambda函数
()=&gt; ()=&gt;变量 * 2 。
在生成的类(容器)的内部,它生成一个名为 变量的字段 ,具有具有相同名称的捕获变量,而方法b__0()包含lambda的主体。
而不是命名变量的局部变量,它成为容器类(&lt;&gt;&gt; c__displayClass0_0)的字段,
因此递增变量结果转向集装箱类别的字段,并且因为我们获得了一个容器类的实例,用于While循环的所有迭代,我们将获得相同的输出
。 i.sstatic.net/1ds8x.png“ alt =“在此处输入图像说明”>
您可以通过将循环体内的捕获变量重新分配到新的本地变量中,以防新的局部变量
,此行为仍然有效使用.NET 8预览,我发现这种行为非常越来越大。
As others said It has nothing to do with loops.It is the effect of the variable capture mechanism in the body of anonymous functions in C#.
When you define lambda as your example ;
Compiler generates a container class like <>c__DisplayClass0_0 for the lambda function
() => () => variable * 2.
Inside of the generated class(container) it generates a field named as variable, having a captured variable with the same name and the method b__0() containing the body of the lambda.
And than local variable named variable, becomes a field of a container class(<>c__DisplayClass0_0)
So incrementing variable results in turn incrementing field of container class and because we get one instance of the container class for all iterations of the while loop,we get same output that is 10.
You can prevent by reassigning captured variable inside the body of the loop to a new local variable
By the way,this behaviour still is valid with .Net 8 Preview and I find this behaviour is very buggy and deceptive.
它称为封闭问题,
只需使用复制变量即可完成。
It is called the closure problem,
simply use a copy variable, and it's done.
由于这里没有人直接引用 ecma-334 :
在规格中进一步,
,是的,我想应该提到的是,在C ++中没有发生此问题,因为您可以选择该变量是通过值或参考捕获的(请参阅: lambda捕获)。
Since no one here directly quoted ECMA-334:
Further on in the spec,
Oh yea, I guess it should be mentioned that in C++ this problem doesn't occur because you can choose if the variable is captured by value or by reference (see: Lambda capture).