如果你像我一样,当你看到为你产生的新的语言特性时,你很好了解新语言特性。C#中的闭包没有区别。在C#闭包的表面下有很多事情发生。查看C#3.0编译器生成的所有代码能真正的帮助你明白发生了什么。 在Reflector的帮助下,我们可以学到很多。我用一个相当简单的C#3.0程序开始:
Code
class Program
{
static void Main(string[] args)
{
int counter = 0;
IEnumerable<int> values = Utilities.Generate(20, () => counter++);
Console.WriteLine("Current Counter: {0}", counter);
foreach (int num in values)
Console.WriteLine(num);
Console.WriteLine("Current Counter: {0}", counter);
foreach (int num in values)
Console.WriteLine(num);
Console.WriteLine("Current Counter: {0}", counter);
}
}
public static class Utilities
{
public static IEnumerable<T> Generate<T>(int num, Func<T> generator)
{
int index = 0;
while (index++ < num)
yield return generator();
}
}
这个程序的输出相当简单,但是给我们展示了一些关于闭包和延迟执行的东西。
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
public static class Utilities
{
// Methods
public static IEnumerable<T> Generate<T>(int num, Func<T> generator)
{
GenerateEnumerator<T> d__ = new GenerateEnumerator<T>(-2);
d__.currentNumber = num;
d__.generatorFunc = generator;
return d__;
}
// Nested Types
private sealed class GenerateEnumerator<T> :
IEnumerable<T>, IEnumerable, IEnumerator<T>, IEnumerator, IDisposable
{
// Fields
private int state;
private T current;
public Func<T> generatorFunc;
public int currentNumber;
private int initialThreadId;
public int index;
public Func<T> generator;
public int num;
// Methods
public GenerateEnumerator(int initialState)
{
this.state = initialState;
this.initialThreadId = Thread.CurrentThread.ManagedThreadId;
}
public bool MoveNext()
{
switch (this.state)
{
case 0:
this.state = -1;
this.index = 0;
while (this.index++ < this.num)
{
this.current = this.generator();
this.state = 1;
return true;
}
break;
case 1:
while (this.index++ < this.num)
{
this.current = this.generator();
this.state = 1;
return true;
}
break;
}
return false;
}
IEnumerator<T> IEnumerable<T>.GetEnumerator()
{
Utilities.GenerateEnumerator<T> d__;
if ((Thread.CurrentThread.ManagedThreadId ==
this.initialThreadId) && (this.state == -2))
{
this.state = 0;
d__ = (Utilities.GenerateEnumerator<T>)this;
}
else
d__ = new Utilities.GenerateEnumerator<T>(0);
d__.num = this.currentNumber;
d__.generator = this.generatorFunc;
return d__;
}
IEnumerator IEnumerable.GetEnumerator()
{
return ((IEnumerable<T>)this).GetEnumerator();
}
void IEnumerator.Reset()
{
throw new NotSupportedException();
}
void IDisposable.Dispose()
{
}
T IEnumerator<T>.Current
{
get { return this.current; }
}
object IEnumerator.Current
{
get { return this.current; }
}
}
}
所有的有趣的添加的都在类GenerateEnumerator中。你可以看到这个类包含IEnumerator<T>和IEnumerator的实现。它处理关联到在列表中的当前位置的状态。当你调用产生的任何时候它创建嵌套类。所有的IEnumerator方法在嵌套类中被处理。
闭包也是类和对象(Closures are also classes and objects)
同样的技术被用在闭包,包围在例子中的main方法中的generate方法:
Code
class Program
{
private sealed class GeneratedClosure
{
// Fields
public int counter;
// Methods
public int GeneratedMethod1()
{
return this.counter++;
}
}
private static void Main(string[] args)
{
GeneratedClosure closureObject = new GeneratedClosure();
closureObject.counter = 0;
IEnumerable<int> values = Utilities.Generate<int>(20,
new Func<int>(closureObject.GeneratedMethod1));
Console.WriteLine("Current Counter: {0}", closureObject.counter);
using (IEnumerator<int> generatedEnumerator = values.GetEnumerator())
{
while (generatedEnumerator.MoveNext())
{
int num = generatedEnumerator.Current;
Console.WriteLine(num);
}
}
Console.WriteLine("Current Counter: {0}", closureObject.counter);
foreach (int num in values)
{
Console.WriteLine(num);
}
Console.WriteLine("Current Counter: {0}", closureObject.counter);
}
}
编译器创建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>