今天老赵在园子里发了一篇文章"警惕匿名方法造成的变量共享",立即引起了大家的广泛关注(老赵就是园子的"人气天王",呵呵),而且这个问题园子里也有其它几篇文章做了研究
比如"闭包","《你不常用的c#之三》:Action 之怪状 "
如果只是停留在c#2.0/3.0的"简捷且优雅"的代码风格上,初学者确实难理解这个"怪"现象,前二天买了本anytao的“你必须知道的.net”,里面提供了一种研究这类表面"怪"现象的基本方法--IL分析,并推荐了大名鼎鼎的反编译工具"Reflector",下面利用这个工具对其分析一二(高手就不必看了,权当给初学者一些参考)
原始代码一(摘自"《你不常用的c#之三》:Action 之怪状"一文):
![ContractedBlock.gif](/Images/OutliningIndicators/ContractedBlock.gif)
![ExpandedBlockStart.gif](/Images/OutliningIndicators/ExpandedBlockStart.gif)
using System;
using System.Collections.Generic;
namespace ConsoleTest
{
class Program
{
static void Main(string[] args)
{
List<Action> ls = new List<Action>();
for (int i = 0; i < 10; i++)
{
ls.Add(() => Console.WriteLine(i));
}
foreach (Action action in ls)
{
action();
}
System.Console.Read();
}
}
}
结果:一连输出了10行完全相同的"10"(可能并没有按代码编写者的"意图",输出0到9),why?
打开Relector,先做一些设置:打开"View"菜单-->选择"Options",先去掉Show PDB symbols前的勾,然后把Optimization后的下拉框改为".Net 1.0"(众多的"语法糖",比如匿名方法,扩展方法等都是在1.0版本以后出现的,这样设置的目的是去掉这些华丽的外衣,直接反应出原始的c#代码),刚才的代码经过反编译后,大概如下:
private sealed class <> c__DisplayClass2
{
// Fields
public int i;
// Methods
public void < Main > b__0()
{
Console.WriteLine( this .i);
}
}
private static void Main( string [] args)
{
List < Action > list = new List < Action > ();
Action item = null ;
<>c__DisplayClass2 class2 = new <> c__DisplayClass2();
class2.i = 0 ;
while (class2.i < 10 )
{
if (item == null )
{
item = new Action(class2.<Main> b__0);
}
list.Add(item);
class2.i++ ;
}
foreach (Action action2 in list)
{
action2();
}
Console.Read();
}
可以看出,
1.编译器自动生成了一个密封类:<>c__DisplayClass2,里面有一个公有字段i,以及一个公共方法<Main>b__0()--用来输出i
2.再看Main方法中的高亮部分,自始至终,<>c__DisplayClass2就只生成了一个实例class2,至于下面的while里变来变去,也只不过在改变i这个变量(也就是实例class2的成员i),而我们知道“类(class)”是引用类型,实际上class2不过是个引用而已,所以每次用new Action(class2.<Main>b__0)生成item,再list.Add(item)进去后,每个item调用的都是同一个引用,因此最终一连输出10行相同的结果--即数字10,也就是理所当然了
把代码1,稍作修改,如下:
using System.Collections.Generic;
namespace ConsoleTest
{
class Program
{
static void Main( string [] args)
{
List < Action > ls = new List < Action > ();
for ( int i = 0 ; i < 10 ; i ++ )
{
int lp = i;
ls.Add(() => Console.WriteLine(lp));
}
foreach (Action action in ls)
{
action();
}
System.Console.Read();
}
}
}
private sealed class <> c__DisplayClass1
{
// Fields
public int tp;
// Methods
public void < Main > b__0()
{
Console.WriteLine( this .tp);
}
}
private static void Main( string [] args)
{
List < Action > list = new List < Action > ();
for (int i = 0; i < 10; i++ )
{
<>c__DisplayClass1 class2 = new <> c__DisplayClass1();
class2.tp = i;
list.Add(new Action(class2.<Main> b__0));
}
foreach (Action action in list)
{
action();
}
Console.Read();
}
![ContractedBlock.gif](/Images/OutliningIndicators/ContractedBlock.gif)
![ExpandedBlockStart.gif](/Images/OutliningIndicators/ExpandedBlockStart.gif)
using System;
using System.Collections.Generic;
namespace ConsoleTest
{
class Program
{
static void Main(string[] args)
{
List<Action> ls = new List<Action>();
for (int i = 0; i < 10; i++)
{
ls.Add(() => Console.WriteLine(i));
ls[i]();
}
System.Console.Read();
}
}
}