34.闭包中的变量捕获结果

  1. 34.闭包中的变量捕获结果
    1. 34.1 题目
    2. 34.2 深入解析
    3. 34.3 答题示例
    4. 34.4 关键词联想

34.闭包中的变量捕获结果


34.1 题目

static void Main(string[] args)
{
    Action action = null;
    for (int i = 0; i < 10; i++)
    {
        action += () =>
        {
            Console.WriteLine(i);
        };
    }
    action();
}

请问上面代码的最终打印结果是什么?为什么?如果我们希望打印出0~9,应该如何修改代码?


34.2 深入解析

上述代码的最终打印结果是全都是10。

这是因为在 C# 中,闭包捕获的是变量 i 本身(编译器生成的闭包类字段语义),而非循环每次迭代的瞬时值。循环结束后 i 为 10,故所有委托执行时看到的都是 10。
如果我们希望打印出0~9,可以用一个临时变量记录。

static void Main(string[] args)
{
    Action action = null;
    for (int i = 0; i < 10; i++)
    {
        int j = i;
        action += () =>
        {
            Console.WriteLine(j);
        };
    }
    action();
}

每次循环进入新的 int j = i 时,都会为当前迭代产生独立的局部变量;各委托捕获各自的 jaction() 调用时会依次打印 0~9。


34.3 答题示例

上述代码的最终打印结果是连续输出10个”10”。

原因是C#中闭包会捕获外部变量的引用而非值。循环中的匿名方法捕获的是变量i的引用,而非每次循环时i的具体值。当循环结束后,i的值已自增到10,因此调用action()时,所有匿名方法都会打印此时i的引用值10。

若要打印0~9,需让每个匿名方法捕获当前循环的i值,可在循环内定义临时变量存储当前i的值,使闭包捕获临时变量的引用。修改如下:
在循环体内添加int j = i;,然后匿名方法中打印j。此时每个j都是当前循环的i值的拷贝,循环结束后各自保留对应的值,调用时会依次打印0到9。


34.4 关键词联想

  • 闭包(Closure)
  • 变量引用捕获
  • Action委托
  • 匿名方法
  • 循环变量(i)
  • 临时变量(值拷贝)
  • 变量作用域
  • 委托调用时机


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com

×

喜欢就点赞,疼爱就打赏