为什么c#不';t为匿名委托调用保留上下文

本文关键字:调用 上下文 保留 为什么 | 更新日期: 2023-09-27 18:20:25

我有以下方法:

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)?

为什么c#不';t为匿名委托调用保留上下文

这里的问题是有一个i变量和十个x实例/副本。每个lambda都获得对单个变量ix的一个实例的引用。每个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++;
};

DoAction生成线程,并立即返回。当线程从随机睡眠中醒来时,循环将结束,i的值将一直前进到10。另一方面,x的值在调用之前被捕获并冻结,因此您将以随机顺序获得从09的所有值,这取决于基于随机数生成器的每个线程的睡眠时间。

我认为使用java或任何面向对象的语言都会得到相同的结果(不确定,但在这里它似乎是合乎逻辑的)。

i的范围用于整个循环,x的范围用于每次出现。

Resharper可以帮你找出这类问题的症结所在。