C# 是委托中的值类型,与父方法中的值类型不同

C# are value types in a delegate different to those in the parent method

考虑以下代码块:

int noThreads = noProcessors - 1;
var countdownEvent = new CountdownEvent(noThreads);
if (noThreads == 0)
    noThreads = 1;

float blockSize = (float)jobList.Count / noThreads;

int startIndex, stopIndex;
for (int i = 0; i < noThreads; i++)
{
    startIndex = i * (int)blockSize;
    stopIndex = (i + 1) * (int)blockSize;
    if (i == noThreads - 1)
        stopIndex = jobList.Count;

    new Thread(delegate()
    {
        for (int j = startIndex; j < stopIndex; j++)
        {
            CreateJobAndAddToCollection(jobs[j], jobList, j);
        }

        countdownEvent.Signal();
    }).Start();                
}


countdownEvent.Wait();

我想知道委托内的变量 startIndex 和 stopIndex 是否指向与委托外的同名变量相同的位置。从我的测试来看,我认为它们确实如此,这让我感到惊讶,因为它们是值类型,而且我原以为委托中的代码将被视为一个单独的方法。 抱歉,如果之前有人问过这个问题,我找不到任何其他相关信息。

I want to know whether the variables startIndex and stopIndex within the delegate point to the same location as their namesakes outside the delegate.

是的,他们有。变量本身被捕获 - 在委托内所做的更改将在委托外可见,反之亦然。

但是,这只是一个问题,因为您已经在 循环之外声明了 startIndexstopIndex 。如果您在循环中 声明它们,则每次迭代将使用一对单独的变量。

for (int i = 0; i < noThreads; i++)
{
    int startIndex = i * (int)blockSize;
    int stopIndex = (i + 1) * (int)blockSize;
    // Code as before
}

每个委托现在将捕获一个单独的 startIndexstopIndex 对,循环中的其余代码不会修改这些变量,因此您将 "safe".换句话说,如果您的循环代码包含:

开始索引++; stopIndex++;

最后,委托仍然会看到这些变化(假设新线程在迭代结束后实际执行了委托...)但是 next 上的新变量对 循环的迭代将完全独立于此迭代。

避免线程,这里有一个简单的例子来展示这一切:

using System;
using System.Collections.Generic;

class Test
{
    static void Main()
    {
        int outerCounter = 0;
        var actions = new List<Action>();
        for (int i = 0; i < 5; i++)
        {
            outerCounter = i * 10;
            int innerCounter = i * 10;
            // Using an anonymous method here as per question;
            // you'd see the same behaviour with a lambda expression.
            actions.Add(delegate {
                Console.WriteLine("{0}: {1}", outerCounter, innerCounter);
            });

            innerCounter++;
        }

        foreach (var action in actions)
        {
            action();
        }
    }
}

输出为:

40: 1
40: 11
40: 21
40: 31
40: 41

这表明:

  • 所有代表都使用相同的 outerCounter 变量,该变量在 for 循环结束时的值为 40。
  • 每个委托都使用自己的 innerCounter 变量,当委托 创建 时,该变量的值为 i * 10,但 i * 10 + 1由于 innerCounter++ 行,代表 已执行 时。

它们通过引用传递给委托方法。