C# 在C中捕获循环中的变量#

C# 在C中捕获循环中的变量#,c#,closures,captured-variable,C#,Closures,Captured Variable,我遇到了一个关于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.WriteL

我遇到了一个关于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());
}
List actions=new List();
int变量=0;
while(变量<5)
{
actions.Add(()=>变量*2);
++可变的;
}
foreach(var行动中的行为)
{
Console.WriteLine(act.Invoke());
}
我希望它输出0、2、4、6、8。然而,它实际上输出五个10

这似乎是由于所有操作都引用了一个捕获的变量。因此,当它们被调用时,它们都有相同的输出


是否有办法绕过此限制,使每个操作实例都有自己捕获的变量?

是-在循环中复制变量:

while (variable < 5)
{
    int copy = variable;
    actions.Add(() => copy * 2);
    ++ variable;
}
请参见C#3.0规范的第7.14.4.2节了解更多详细信息,my也有更多示例


请注意,从C#5编译器及更高版本开始(即使在指定C#的早期版本时),
foreach
的行为发生了更改,因此不再需要进行本地复制。有关更多详细信息,请参阅。

是,您需要在循环中定义变量的作用域,并通过以下方式将其传递给lambda:

List<Func<int>> actions = new List<Func<int>>();

int variable = 0;
while (variable < 5)
{
    int variable1 = variable;
    actions.Add(() => variable1 * 2);
    ++variable;
}

foreach (var act in actions)
{
    Console.WriteLine(act.Invoke());
}

Console.ReadLine();
List actions=new List();
int变量=0;
while(变量<5)
{
int variable1=变量;
actions.Add(()=>variable1*2);
++可变的;
}
foreach(var行动中的行为)
{
Console.WriteLine(act.Invoke());
}
Console.ReadLine();

解决方法是将所需的值存储在代理变量中,并捕获该变量

while(变量<5)
{
int copy=变量;
操作。添加(()=>复制*2);
++可变的;
}

我相信你所经历的就是所谓的结束。lamba引用了一个作用域在函数本身之外的变量。在调用lamba之前,不会对其进行解释,一旦调用,它将获得变量在执行时的值。

在多线程(C#,4.0)中也会出现同样的情况

请参阅以下代码:

目的是按顺序打印1,2,3,4,5

for (int counter = 1; counter <= 5; counter++)
{
    new Thread (() => Console.Write (counter)).Start();
}
for(int counter=1;计数器控制台.Write(counter)).Start();
}
输出很有趣!(可能是21334…)

唯一的解决方案是使用局部变量

for (int counter = 1; counter <= 5; counter++)
{
    int localVar= counter;
    new Thread (() => Console.Write (localVar)).Start();
}
for(int counter=1;计数器控制台.Write(localVar)).Start();
}

在幕后,编译器正在为方法调用生成一个表示闭包的类。它在循环的每次迭代中都使用闭包类的单个实例。代码如下所示,这使得更容易理解错误发生的原因:

void Main()
{
    List<Func<int>> actions = new List<Func<int>>();

    int variable = 0;

    var closure = new CompilerGeneratedClosure();

    Func<int> anonymousMethodAction = null;

    while (closure.variable < 5)
    {
        if(anonymousMethodAction == null)
            anonymousMethodAction = new Func<int>(closure.YourAnonymousMethod);

        //we're re-adding the same function 
        actions.Add(anonymousMethodAction);

        ++closure.variable;
    }

    foreach (var act in actions)
    {
        Console.WriteLine(act.Invoke());
    }
}

class CompilerGeneratedClosure
{
    public int variable;

    public int YourAnonymousMethod()
    {
        return this.variable * 2;
    }
}
void Main()
{
列表操作=新建列表();
int变量=0;
var closure=new CompilerGeneratedClosure();
Func anonymousMethodAction=null;
while(closure.variable<5)
{
if(anonymousMethodAction==null)
anonymousMethodAction=newfunc(closure.yournamonymousMethod);
//我们正在添加相同的函数
actions.Add(匿名方法操作);
++闭包变量;
}
foreach(var行动中的行为)
{
Console.WriteLine(act.Invoke());
}
}
类编译器生成的闭包
{
公共int变量;
public int-YourAnonymousMethod()
{
返回这个变量*2;
}
}
这实际上不是您示例中的编译代码,但我已经检查了自己的代码,这看起来非常像编译器实际生成的代码。

这与循环无关。 触发此行为是因为使用lambda表达式
()=>变量*2
,其中外部作用域
变量
实际上未在lambda的内部作用域中定义

Lambda表达式(在C#3+中,以及在C#2中的匿名方法)仍然会创建实际的方法。将变量传递给这些方法会遇到一些难题(传递值?传递引用?C#会伴随着引用-但这会导致另一个问题,即引用可能比实际变量长)C#解决所有这些难题的方法是创建一个新的助手类(“闭包”)与lambda表达式中使用的局部变量相对应的字段,以及与实际lambda方法相对应的方法。代码中对
variable
的任何更改实际上都会转换为该
ClosureClass.variable

因此,while循环不断更新
ClosureClass.variable
,直到它达到10,然后for循环执行操作,这些操作都在同一
ClosureClass.variable
上运行

要获得预期结果,您需要在循环变量和正在关闭的变量之间创建分隔。您可以通过引入另一个变量来实现这一点,即:

List<Func<int>> actions = new List<Func<int>>();
int variable = 0;
while (variable < 5)
{
    var t = variable; // now t will be closured (i.e. replaced by a field in the new class)
    actions.Add(() => t * 2);
    ++variable; // changing variable won't affect the closured variable t
}
foreach (var act in actions)
{
    Console.WriteLine(act.Invoke());
}
List actions=new List();
int变量=0;
while(变量<5)
{
var t=variable;//现在t将被关闭(即被新类中的字段替换)
动作。添加(()=>t*2);
++variable;//更改变量不会影响已关闭的变量t
}
foreach(var行动中的行为)
{
Console.WriteLine(act.Invoke());
}
您还可以将闭包移动到另一个方法以创建此分隔:

List<Func<int>> actions = new List<Func<int>>();

int variable = 0;
while (variable < 5)
{
    actions.Add(Mult(variable));
    ++variable;
}

foreach (var act in actions)
{
    Console.WriteLine(act.Invoke());
}
List actions=new List();
int变量=0;
while(变量<5)
{
actions.Add(Mult(变量));
++可变的;
}
foreach(var行动中的行为)
{
Console.WriteLine(act.Invoke());
}
可以将Mult实现为lambda表达式(隐式闭包)

static Func Mult(int i)
{
返回()=>i*2;
}
或者使用实际的帮助器类:

public class Helper
{
    public int _i;
    public Helper(int i)
    {
        _i = i;
    }
    public int Method()
    {
        return _i * 2;
    }
}

static Func<int> Mult(int i)
{
    Helper help = new Helper(i);
    return help.Method;
}
公共类助手
{
公共国际;
公共助理(国际一)
{
_i=i;
}
公共int方法()
{
返回i*2;
}
}
静态函数多(int i)
{
助手帮助=新助手(i);
返回帮助。方法;
}
在任何情况下,“闭包”都不是与循环相关的概念,而是与匿名方法/lambda表达式使用局部作用域变量相关的概念,尽管有些不谨慎
List<Func<int>> actions = new List<Func<int>>();

int variable = 0;
while (variable < 5)
{
    actions.Add(Mult(variable));
    ++variable;
}

foreach (var act in actions)
{
    Console.WriteLine(act.Invoke());
}
static Func<int> Mult(int i)
{
    return () => i * 2;
}
public class Helper
{
    public int _i;
    public Helper(int i)
    {
        _i = i;
    }
    public int Method()
    {
        return _i * 2;
    }
}

static Func<int> Mult(int i)
{
    Helper help = new Helper(i);
    return help.Method;
}
List<Func<int>> actions = new List<Func<int>>();

int variable = 0;
while (variable < 5)
{
    int i = variable;
    actions.Add(() => i * 2);
    ++ variable;
}

foreach (var act in actions)
{
    Console.WriteLine(act.Invoke());
}
for (for-initializer; for-condition; for-iterator) embedded-statement
{
    for-initializer;
    while (for-condition) {
        embedded-statement;
    LLoop: for-iterator;
    }
}
static void F() {
  for (int i = 0; i < 3; i++) {
    int x = i * 2 + 1;
    ...
  }
}
static void F() {
  int x;
  for (int i = 0; i < 3; i++) {
    x = i * 2 + 1;
    ...
  }
}
using System;

delegate void D();

class Test{
  static D[] F() {
    D[] result = new D[3];
    for (int i = 0; i < 3; i++) {
      int x = i * 2 + 1;
      result[i] = () => { Console.WriteLine(x); };
    }
  return result;
  }
  static void Main() {
    foreach (D d in F()) d();
  }
}
1
3
5
static D[] F() {
  D[] result = new D[3];
  int x;
  for (int i = 0; i < 3; i++) {
    x = i * 2 + 1;
    result[i] = () => { Console.WriteLine(x); };
  }
  return result;
}
5
5
5
static D[] F() {
  D[] result = new D[3];
  for (int i = 0; i < 3; i++) {
    result[i] = () => { Console.WriteLine(i); };
  }
  return result;
}
3
3
3
for (int n=0; n < 10; n++) //forloop syntax
foreach (string item in foo) foreach syntax