zoukankan      html  css  js  c++  java
  • 防止Lambda的各种坑爹(二)

      2.循环内的被捕获的变量。

      首先看一段代码:

    View Code
     1 using System;
     2 using System.Collections.Generic;
     3 using System.Linq;
     4 using System.Text;
     5 using System.Threading.Tasks;
     6 
     7 namespace Lambda2
     8 {
     9     class Program
    10     {
    11         static void Main(string[] args)
    12         {
    13             List<Action> list = new List<Action>();
    14             for (int i = 0; i < 3; i++)
    15             {
    16                 int current = i;
    17                 list.Add(() =>
    18                 {
    19                     Console.Write(current);
    20                     current++;
    21                 });
    22             }
    23 
    24             foreach (var item in list)
    25             {
    26                 item();
    27             }
    28             list.First()();
    29             list.First()();
    30 
    31             Console.Read();
    32         }
    33     }
    34 }

      你能猜出输出的是什么吗?如果你的答案是01212,那么恭喜你,你的答案是正确的。这里可以看出:当在Lambda中捕获一个变量时,被捕获的是变量的实例。也就是说,循环第一次捕获的变量将有别与循环第二次捕获的变量,就像有3个current变量一样,全部叫做current,他们一个接一个的创建。

      代码会创建3个不同的委托---每次循环都会创建一个,添加到一个List集合中。现在,由于current变量是在循环内声明的,所以每次循环迭代。他都会被创建。这样每次委托捕获到的都是不同的current变量的值。所以一次调用每个委托。输出的结果依次是0 1 2。然后我们在执行2个第一个委托,由于在执行了current++,所以依次再输出 1 2。

      我想你一定不奇怪为什么每次的current变量的值不同,因为这个看上次似乎是理所当然的。是这样吗?我们通过ILDASM查看对应生成的IL代码:发现编译器依旧为我们生成的了一个类DisPlayClass1,这个类包装了current变量和委托包装的方法 <Main>b__0:void: Console.Write(current);current++。

      同时可以看到new DisplayClass1的位置在循环内部

      对应的C#代码我想是这样的

    View Code
     1 using System;
     2 using System.Collections.Generic;
     3 using System.Linq;
     4 using System.Text;
     5 using System.Threading.Tasks;
     6 
     7 namespace Lambda2
     8 {
     9     class Program
    10     {
    11         private class DisplayClass1
    12         {
    13             public int current;
    14 
    15             public void F()
    16             {
    17                 Console.Write(current);
    18                 current++;
    19             }
    20         }
    21 
    22         static void Main(string[] args)
    23         {
    24 
    25             List<Action> list = new List<Action>();
    26             for (int i = 0; i < 3; i++)
    27             {
    28                 DisplayClass1 d = new DisplayClass1();
    29                 d.current = i;
    30                 list.Add(d.F);
    31             }
    32 
    33             foreach (var item in list)
    34             {
    35                 item();
    36             }
    37             list.First()();
    38             list.First()();
    39 
    40             Console.Read();
    41         }
    42     }
    43 }

      好了,这样的话就有了一个新问题,应该思考的一点是,如果我们移除current变量,直接用for循环中的i的代替的话,那么会发生什么呢?在这种情况下,所以的循环内的委托共享的是一个变量i。输出的将是3 4 5 6 7。之所以这样,是因为在循环结束时,i的值是3(同时要注意的是,委托内的i++不会现在执行)。之后的每次调用委托都会使i++,每个委托都是调用的同一个变量i。如下代码将证实这一点:

    View Code
     1 using System;
     2 using System.Collections.Generic;
     3 using System.Linq;
     4 using System.Text;
     5 using System.Threading.Tasks;
     6 
     7 namespace Lambda2
     8 {
     9     class Program
    10     {
    11         static void Main(string[] args)
    12         {
    13 
    14             List<Action> list = new List<Action>();
    15             for (int i = 0; i < 3; i++)
    16             {
    17                 list.Add(() =>
    18                 {
    19                     Console.Write(i);
    20                     i++;
    21                 });
    22             }
    23 
    24             foreach (var item in list)
    25             {
    26                 item();
    27             }
    28             list.First()();
    29             list.First()();
    30 
    31             Console.Read();
    32         }
    33     }
    34 }

      同时看到对应的IL代码,new DisplayClass1的位置在循环外部

      好了,这个提醒我们以后在循环内部使用Lambda表达式的时候需要注意的地方。

  • 相关阅读:
    旋转数组的最小数字(JAVA)
    两个队列实现栈&两个栈实现队列(JAVA)
    重建二叉树(JAVA)
    二维数组的查找(JAVA)
    Java垃圾回收机制概述
    前端开发环境
    Java语法糖 : try-with-resources
    立个Flag (20180617-20181231)
    关于标签的整理
    Java反射机制
  • 原文地址:https://www.cnblogs.com/LoveJerryZhang/p/2800651.html
Copyright © 2011-2022 走看看