2012-01-13 34 views
5

我有以下方法:为什么c#不保留匿名委托调用的上下文?

static Random rr = new Random(); 
static void DoAction(Action a) 
{ 
    ThreadPool.QueueUserWorkItem(par => 
    { 
     Thread.Sleep(rr.Next(200)); 
     a.Invoke(); 
    }); 
} 

现在我把这种在一个for循环是这样的:

for (int i = 0; i < 10; i++) 
{ 
    var x = i; 

    DoAction(() => 
    { 
     Console.WriteLine(i); // scenario 1 
     //Console.WriteLine(x); // scenario 2 
    }); 
} 

在场景1的输出是:10 10 10 10 ... 10
在场景2的输出是: 2 6 5 8 4 ... 0(0到9的随机排列)

你如何解释这个? c#不应该为匿名委托调用保留变量(这里是i)?

+2

但保留( '捕获')的*变量*'i'是*正是*发生了什么事! *尽管你希望它发生*,但是在代表成立时保留* i的**值**。 – AakashM 2012-01-13 16:38:37

+2

顺便提及,ReSharper的将发出警告关于[访问改性闭合](http://confluence.jetbrains.net/display/ReSharper/Access+to+modified+closure)时与此代码呈现;你可能会发现有帮助的解释。 – AakashM 2012-01-13 16:40:38

+0

http://blogs.msdn.com/b/ericlippert/archive/2009/11/12/closing-over-the-loop-variable-considered-harmful.aspx – 2012-01-13 17:03:31

回答

10

这里的问题是有一个i变量和x的十个实例/副本。每个lambda获得对单个变量i的引用和x的一个实例。每个x仅写入一次,因此每个lambda都会看到写入其所引用值的一个值。

变量i写入,直到达到10无的lambda表达式的运行,直到循环完成,使他们都看到的i最终值是10

我觉得这个例子是,如果你更清楚一点它改写如下

int i = 0; // Single i for every iteration of the loop 
while (i < 10) { 
    int x = i; // New x for every iteration of the loop 
    DoAction(() => { 
    Console.WriteLine(i); 
    Console.WriteLine(x); 
    }); 
    i++; 
}; 
+1

更尖锐地指出,每'x'不只是写只有一次,但是因为它每次被初始化,所以是一个不同的'x'。循环的生命周期中有10个'x',只有一个'i'。 – 2012-01-13 17:05:27

1

我认为你会得到与Java或任何面向对象语言相同的结果(不知道,但这里似乎是合乎逻辑)。

i的范围适用于整个循环,x的范围适用于每次发生。

Resharper帮助您找到这类问题的首要位置。

1

DoAction产生线程,并立即返回。由线程从它的随机睡眠唤醒时间,循环将完成,并i值将拥有先进一路10 x的价值,而另一方面,被捕获并呼叫之前冻结,因此您将以随机顺序从09获取所有值,具体取决于每个线程根据您的随机数生成器进入睡眠的时间。