For和Foreach处理匿名函数的差别

有一天看C#本质论,突然发现For和Foreach对匿名函数处理的方式不同,差别比较隐蔽,因此想跟大家分享 一下。

一.差别

看代码,很简单,只有三个方法:
1:Main方法,负责调用ForTest和ForeachTest方法:
For和Foreach处理匿名函数的差别
2:ForTest方法,包含如下内容:

  1. 定义一个Action委托列表 ;
  2. 通过for循环向Action列表中添加匿名函数,匿名函数用来输出变量“i”;
  3. 循环Action列表,执行匿名函数;
    For和Foreach处理匿名函数的差别

3:ForeachTest方法,包含如下内容:

  1. 定义一个Action委托列表;
  2. 定义一个数组;
  3. 通过foreach循环向Action列表中添加匿名函数,匿名函数用来输出变量“i“;
  4. 循环Action列表,执行匿名函数;

For和Foreach处理匿名函数的差别
4:运行代码,看结果:
1:ForTest执行结果都是10。
2:ForeachTest执行结果是0…9。
For和Foreach处理匿名函数的差别
为什么是这样?接着往后看。

二.原因

1:是因为匿名函数在引用外部变量时,编译器会生成一个匿名类,匿名类包含如下内容:

  1. 对应外部变量会生成一个实例字段;
  2. 包含一个表达式以及对表达式求值所需的变量

2:当执行表达式的时候,实际上输出的是匿名类实例字段。在ForTest方法中,因为只会实例化一次匿名类,所以实例字段会被反复修改,当执行action.invoke时,此时的i=10,导致实例字段=10,所以每次输出的都是10.
3:但是在ForeachTest方法中,因为每次循环都会实例化一次匿名类,所以实例字段会保留每一次循环的值,当执行action.invoke时,是每个匿名实例单独执行,所以会依次输出0…9。

三.验证

用detPeek看IL代码
1:可以看到编译器为ForTest生成了一个匿名类,在这个匿名类中包含一个实例字段“i”,和表达式以及表达式需要的变量。
For和Foreach处理匿名函数的差别
2:可以看到编译器为ForeachTest生成了一个匿名类,在这个匿名类中包含一个实例字段“i”,和表达式以及表达式需要的变量。
For和Foreach处理匿名函数的差别
3:可以看到ForTest方法在执行for方法循环前,就创建了一个匿名类实例
For和Foreach处理匿名函数的差别
4:可以看到ForeachTest方法在循环中创建了匿名类实例
For和Foreach处理匿名函数的差别

四.结论

1:for输出的结果是一样的,是因为只创建了一个匿名实例,而foreach则会为每一次循环单独创建一个实例。

五.备注

1:for循环不一定每次都是10,这取决于action.invoke在执行时i的值。因为是多线程,所以不能保证每次执行是“i”都等于10.例如有可能是:3、3、3、3、3和10、10、10、10、10