[翻译]C#闭包内幕(Looking Inside C# Closures)

    如果你像我一样,当你看到为你产生的新的语言特性时,你很好了解新语言特性。C#中的闭包没有区别。在C#闭包的表面下有很多事情发生。查看C#3.0编译器生成的所有代码能真正的帮助你明白发生了什么。 Reflector的帮助下,我们可以学到很多。我用一个相当简单的C#3.0程序开始:

Code

这个程序的输出相当简单,但是给我们展示了一些关于闭包和延迟执行的东西。

 

Current Counter: 0

0

1

2

...

17

18

19

Current Counter: 20

20

21

...

38

39

Current Counter: 40

 

    这里可以看到几点:第一,注意到在定义队列后,counter的值是0.那是因为枚举使用延迟执行。枚举没有发生直到一些调用代码要检查枚举。通过查看第一和第二次的枚举后的counter的值,你可以看到这些。注意到在一旦完成枚举队列后,counter的值为20。然后,你看到在再次枚举队列后,counter的值为40。并且,注意到每次你枚举队列时,返回的队列改变。Charlie Calvert这里很好的揭示了这一概念。

    现在,让我们看一看它在内部是如何工作的。闭包是一种数据结构,拥有表达式和为了计算表达式必要绑定到包含变量的环境。好吧,这有些拗口。有时,在代码中能更容易明白。因此让我们来启动Reflector,看看编译器生成的。Reflector有一个很好的选项你可以确定Reflector应该反汇编到哪个版本的.NET。为了帖子,我选择Reflector生成.NET1.1的代码。现在,那导致在代码的体积上有很大的增长,它的可读性,事实上,C#编译器甚至没有编译过反汇编的代码(更多的是在一分钟内)。但是,它非常清楚的展示了C#为你对所有那些新特性做了些什么。因此,为了文章剩下的部分,我用反汇编的代码,并且修改它以便它是有效的C#。编译器产生的奇怪的变量名称被合法的名称替换掉。构造那些没有编译已经被重新的。

现在我将下结论:在大多数情况下,C#3.0创建来处理闭包(closures)、连续(continuations)(枚举方法(enumerator method))和其它新C#3.0特性的状态。这没有魔法,仅仅很多生成的代码。

 

创建一个枚举(Enumerator)类(Creating an Enumerator class)

让我们使用泛型方法开始。泛型创建队列,使用yield返回上下文相关的关键字。Yield Return创建了一个嵌套枚举类来生成队列,并且它处理所有的创建和使用队列的工作。

Code

 

所有的有趣的添加的都在类GenerateEnumerator中。你可以看到这个类包含IEnumerator<T>IEnumerator的实现。它处理关联到在列表中的当前位置的状态。当你调用产生的任何时候它创建嵌套类。所有的IEnumerator方法在嵌套类中被处理。

闭包也是类和对象(Closures are also classes and objects)

同样的技术被用在闭包,包围在例子中的main方法中的generate方法:

Code

 

    编译器创建GeneratedClosure类来包含绑定在表达式需要的环境中的变量。这是一个只包含一个字段,计数器的环境,所以是一个简单的类。注意到字段是公开的,并且闭包类型包含将被绑定到委托(GeneratedMethod1)。GeneratedClosures实现了环境和绑定的变量。闭包中的所有表达式的执行产生在这个嵌套类的上下文。通过查看看起来像C#1.1相等的Main(),你可以领会到我的意思。代替一个简单的int局部变量,编译器创建了一个GeneratedClosured的实例。然后,编译器初始化绑定的变量(closureObject.counter.

      Lambda表达式被一个绑定到实例方法closureObject.GeneratedMethod1的委托。那确保委托在闭包环境的上下文中被计算。

    在这可以看到一些额外的C#行为。虽然Main对队列枚举了一次以上,但编译器只创建了闭包的一个实例。每次环境被重用。这就是为什么最后是40,而不是20.因为同样的原因,第二个队列包含数字20-39。注意到在两种情况下,Main检查闭包内部的绑定变量。那就是从外部范围,闭包环境中的如何改变是可以见的(和可修改的)。最后,我不知道为什么第一次foreach循环和第二次完全不同。如果有人知道,我很感兴趣。

    我希望这次小小的闭包内部之旅会有用。基本要点(最少对我来说)就是:编译器使用大量的常见的构造来创建环境。虽然有助于查看内部代码来理解是如何运行,那只是需要为了帮助弄明白新特性。有助于通过剥去覆盖来移除神秘。在大多数日常工作中,最好使用新语法,让编译器来做工作,把事情完成。但现在,下次有人提到“闭包(closures)”,并且讨论他们是否有用,现在你知道闭包是什么,为什么它是一个有用的构造,并且在C#编译器是如何为你合起来的。

 

源文档 <http://srtsolutions.com/blogs/billwagner/archive/2008/01/22/looking-inside-c-closures.aspx>

posted on 2009-06-20 23:40  齐世昌  阅读(1047)  评论(0编辑  收藏  举报