局部变量作用域突破,c#



我得到了这段代码,

delegate void Printer();
    static void Main(string[] args)
    {
        List<Printer> printers = new List<Printer>();
        for (int i = 0; i < 10; i++)
        {
            printers.Add(delegate { Console.WriteLine(i); });
        }
        foreach (Printer printer in printers)
        {
            printer();
        }
        Console.ReadLine();
    }

这里的输出是'10'十次。

i的作用域是for循环中的with。但是,当我们在外部检索时,我们仍然从i中获取值。

这怎么可能?

您已经修改了闭包。试试这个:

    for (int i = 0; i < 10; i++)
    {
        int ii = i;
        printers.Add(delegate { Console.WriteLine(ii); });
    }

当你在匿名方法中使用access时,它会创建闭包。

委托中的代码在被调用之前不会运行,这发生在第二个循环中。然后它指的是在第一个循环范围内定义的i,但它的电流值-并且由于第一个循环已经完成,因此每次i将为10。

我相信您创建的每个委托都被赋予了与第一个循环相同的作用域,如果这有意义的话。这意味着每个i都有它的委托作为它的范围,并且由于每个委托都在第一个循环的范围内定义,因此每个i也将具有循环作为它的范围,即使委托逻辑在范围之外被称为,如您的示例中所示。

由于i在循环的多个迭代中都是有效的,因此它会被更新,并且在调用委托时总是10。

这解释了为什么下面的代码可以作为修复:

for(int i = 0; i < 10; i++)
{
    var localVar = i; // Only valid within a single iteration of the loop!
    printers.Add(delegate { Console.WriteLine(localVar); });
}

让我们展开循环:

int i=0;
printers.Add(delegate { Console.WriteLine(i); })
i=1;
printers.Add(delegate { Console.WriteLine(i); })
...
i=10;
printers.Add(delegate { Console.WriteLine(i); })

正如您所看到的,i变量在委托中被捕获,并且委托本身直到循环结束才运行,并且变量已经获得了最后一个值(10)。

一个简单的解决方法是将循环变量赋值给一个本地帮助变量
for (int i = 0; i < 10; i++)
{
   var index = i;
   printers.Add(delegate { Console.WriteLine(index); });
}

至于作用域问题,任何捕获的变量的作用域(和生命周期)都有扩展。在lambda/委托中使用的变量将不会被垃圾收集,直到委托本身超出作用域—这对于大型对象来说可能是一个问题。具体来说,c# 5规范的第7.15.5.1节规定:

当一个外部变量被匿名函数引用时外部变量据说是被匿名者捕获的函数。通常,局部变量的生命周期被限制为与其相关联的块或语句的执行(§5.1.7)。但是,捕获的外部变量的生存期为至少扩展到从创建的委托或表达式树为止匿名函数可以进行垃圾收集。

每个委托只在foreach中调用,在for循环之后。此时,闭包捕获的变量i已经是它的最终值,即10。你可以这样解决:

for (int i = 0; i < 10; i++)
{
    var cache = i;
    printers.Add(delegate { Console.WriteLine(cache); });
}

最新更新