C#中匿名方法变量共享原理分析
2011-10-18 17:24 AnyKoro 阅读(410) 评论(0) 编辑 收藏 举报先上几篇我分析中用到例子的文章
进入正题,正向老赵说的那样,c#本来没有什么匿名方法之说,只是编译器施展的魔法。那么我们首先先看看老赵文中提出的,魔法施展的方法。
“编译器将匿名方法中需要访问的所有成员一起包含在闭包中,确保所有的成员调用都符合.NET标准。”
它的处理方式可以用老赵“友好化”处理的类来说明。
源代码:
class TestClass { private void Print(string message) { Console.WriteLine(message); } public void Test() { string[] messages = new string[] { "Hello", "World" }; int index = 0; Action<string> action = (m) => { this.Print((index++) + ". " + m); }; Array.ForEach(messages, action); Console.WriteLine("index = " + index); } }
编译器处理后的代码:
class TestClass { ... private sealed class AutoGeneratedHelperClass { public TestClass m_testClassInstance; public int m_index; public void Action(string m) { this.m_index++; this.m_testClassInstance.Print(m); } } public void TestAfterCompiled() { AutoGeneratedHelperClass helper = new AutoGeneratedHelperClass(); helper.m_testClassInstance = this; helper.m_index = 0; string[] messages = new string[] { "Hello", "World" }; Action<string> action = new Action<string>(helper.Action); Array.ForEach(messages, action); Console.WriteLine(helper.m_index); } }
由此就可以看出编译器是如何实现一个闭包的:
-
编译器自动生成一个私有的内部辅助类,并将其设为sealed,这个类的实例将成为一个闭包对象。
- 如果匿名方法需要访问方法的参数或局部变量,那么该参数或局部变量将“升级”成为辅助类中的公有Field字段。
- 如果匿名方法需要访问类中的其它方法,那么辅助类中将保存类的当前实例。
这三点感觉说得不够详细,我在这里做更多的解释
AutoGeneratedHelperClass是形成匿名方法的关键。根据匿名方法的特点,函数块中使用到了局部变量。这句话可能比较难懂,我们可以看这段代码
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();
}
}
在这里你可以发现,在ls.Add(() => Console.WriteLine(i));这句代码中,使用到了局部变量i。(注意是在函数体内使用到局部变量,而不是作为参数传入。)
有了这个基础后,我们再看之前的,在TestAfterCompiled()中是如何调用这个功能的。我们发现在主要用到了AutoGeneratedHelperClass类,在该方法中,实例化了一个AutoGeneratedHelperClass类,并且通过给该实例对象的字段赋值的手段,来使得可以正常的运行功能。所以,简单理解,在.net中匿名方法的实现,其实就是把匿名方法用到的局部变量封装在一个类(称为N类)中,同时该类还有一个通匿名方法具有相同实现的方法。以此解决了局部变量声明周期的问题。同样的对于匿名方法调用类(使用匿名方法的类,简称O类)方法,也就是在N类中加入一个O类的实例。
<>c__DisplayClass2类就是我们说的N类,然后把注意力放到高亮部分,这个class2就是对应老赵的helper类。由于i赋值的阶段在for之前,所以该类的生成就在循环之前。这样以来就要出问题了。尽管你Add进去的item的形式是对的,但是其所用到得N类却是同一个(类是引用类型),当执行完循环后class2还是class2只是其中的i变了。既然10个action指向同一个N类,那么他们必然打印出10个相同的结果。